Explainer for the Cross-Origin Storage API Google Chrome engineers Thomas Steiner and François Beaufort, along with Thinktecture AG's Christian Liebel, have proposed the Cross-Origin Storage (COS) API, which enables web applications to securely store and retrieve large files across different origins using cryptographic hashes for integrity. The API aims to reduce redundant downloads of shared assets like AI models and WebAssembly modules, addressing scalability and efficiency challenges in current storage isolation practices. This proposal outlines the design of the Cross-Origin Storage COS API, which allows web applications to store and retrieve files across different origins. Building on the File System Living Standard defined by the WHATWG, the COS API facilitates secure cross-origin file storage and retrieval for large assets, such as AI models, WebAssembly Wasm modules, and highly popular JavaScript libraries. Taking inspiration from Cache Digests for HTTP/2 , the API identifies files by their hashes to ensure integrity. Tip Try the proposed API with an extension While this API is not yet natively implemented in browsers, you can experiment with the proposed surface today. Install the Cross-Origin Storage extension https://chromewebstore.google.com/detail/cross-origin-storage/denpnpcgjgikjpoglpjefakmdcbmlgih to inject the navigator.crossOriginStorage polyfill on all pages and test the complete flow. See the source code of the extension https://github.com/web-ai-community/cross-origin-storage-extension and read the instructions https://github.com/web-ai-community/cross-origin-storage-extension?tab=readme-ov-file usage for how to test it. Tip Test with your Vite project If you are building with Vite, you can experiment with COS integration using the experimental vite-plugin-cross-origin-storage https://github.com/tomayac/vite-plugin-cross-origin-storage plugin. Install it with npm install vite-plugin-cross-origin-storage --save-dev and add it to your vite.config.ts . The plugin automatically rewrites static imports to load vendor chunks and other assets from COS, stores newly fetched assets in COS for future use, and falls back gracefully to standard network requests when COS is unavailable or the asset is not yet cached. Thomas Steiner mailto:tomac@google.com , Google Chrome Christian Liebel mailto:christian@liebel.org , Thinktecture AG François Beaufort mailto:fbeaufort@google.com , Google Chrome Issues https://github.com/WICG/cross-origin-storage/issues PRs https://github.com/WICG/cross-origin-storage/pulls - Support this proposal: expression of support https://github.com/WICG/cross-origin-storage/labels/expression%20of%20support The Cross-Origin Storage COS API provides a secure mechanism for web applications to store and retrieve large files across different origins. This allows applications to share common assets—such as AI models, Wasm modules, and popular JavaScript libraries—without redundant downloads. Resources are identified by their cryptographic hashes to ensure data integrity. The API reuses concepts like FileSystemFileHandle from the File System Living Standard , specifically tailored for cross-origin scenarios. The following example demonstrates the basic flow for retrieving a file: js // The hash of the desired file. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; try { const handle = await navigator.crossOriginStorage.requestFileHandle hash ; // The file exists in Cross-Origin Storage. const fileBlob = await handle.getFile ; // Do something with the blob. } catch err { if err.name === 'NotAllowedError' { console.log 'The user agent did not grant permission to access the file.' ; } else if err.name === 'NotFoundError' { console.log 'The file was not found in Cross-Origin Storage.' ; } return; } Caution The authors acknowledge that storage is usually isolated by origin to safeguard user security and privacy. Storing large resources such as AI models separately for each origin, as required by new use cases use-cases , presents a significant scalability and efficiency challenge. For instance, if both example.com and example.org each require the same 8 GB AI model, this would result in 16 GB of downloaded data and a total allocation of 16 GB on the user's device. This proposal introduces mechanisms that uphold protection standards while addressing the inefficiencies of duplicated downloads and storage. COS aims to: - Provide a cross-origin storage mechanism for web applications to store and retrieve large files such as AI models, Wasm modules, and highly popular JavaScript libraries. - Guarantee data integrity and consistency for file identification see Appendix B appendixb-blob-hash-with-the-web-crypto-api . - Make the web more sustainable and ethical by reducing redundant downloads of large resources the user agent may already have stored locally. COS does not aim to: - Replace existing storage solutions such as the Origin Private File System , the Cache API , IndexedDB , or Web Storage . - Replace content delivery networks CDNs . - Allow cross-origin file access without the possibility for the user agent to intervene. - Modify or supersede the same-origin policy. Feedback from developers working with large AI models, Wasm modules, and highly popular JavaScript libraries has highlighted the need for an efficient way to store and retrieve such large files across web applications on different origins. These developers are looking for a standardized solution that allows files to be stored once and accessed by multiple applications, without needing to download and store the files redundantly. COS ensures this is possible while maintaining privacy and security. Joshua Lochner https://huggingface.co/Xenova aka. Xenova from Hugging Face had the following to say in his talk at the 2024 Chrome Web AI Summit https://youtu.be/n18Lrbo8VU8?t=1040 : "One can imagine a browser-based web store for models similar to the Chrome Web Store for extensions. From the user's perspective, they could search for web-compatible models on the Hugging Face hub, install it with a single click, and then access it across multiple domains. Currently, Transformers.js is limited in this regard, since models are cached on a per site or per extension basis." Participants of the Web Machine Learning Working Group at the W3C in their meeting on September 21, 2023, discussed Storage APIs for caching large models https://www.w3.org/2023/09/21-webmachinelearning-minutes.html t03 . A proposal named Hybrid AI Explorations https://github.com/webmachinelearning/proposals/issues/5 listed the following open issues: "If the model runs on the client, large models need to be downloaded, possibly multiple times in different contexts. This incurs a startup latency." "Models are large and can consume significant storage on the client, which needs to be managed." This led to the creation of a dedicated Hybrid AI explainer https://github.com/webmachinelearning/hybrid-ai/blob/main/explainer.md , which in its introduction states: "For example, ML models are large. This creates network cost, transfer time, and storage problems. As mentioned, client capabilities can vary. This creates adaptation, partitioning, and versioning problems. We would like to discuss potential solutions to these problems, such as shared caches, progressive model updates, and capability/requirements negotiation." In their standards position https://github.com/mozilla/standards-positions/issues/1067 issuecomment-2631718109 on the Writing Assistance APIs https://github.com/webmachinelearning/writing-assistance-apis/tree/main , Mozilla engineer Brian Grinstead https://github.com/bgrins wrote: "We acknowledge a downside with this approach related to lack of shared client storage for model weights — it would be a better experience if the browser only had to download large weights one time. We don’t know of a privacy-preserving way to do this, short of high level APIs like these which abstract away the details of inference." Developers working with large AI models can store these models once and access them across multiple web applications. By using the COS API, models can be stored and retrieved based on their hashes, minimizing repeated downloads and storage, ensuring file integrity. An example is Google's Gemma 2 https://huggingface.co/google/gemma-2-2b/tree/main model g-2b-it-gpu-int4.bin https://storage.googleapis.com/jmstore/kaggleweb/grader/g-2b-it-gpu-int4.bin 1.35 GB . Another example is Google's Gemma 1.1 7B https://huggingface.co/google/gemma-1.1-7b-it model gemma-1.1-7b-it 8.60 GB , which can be run in the browser https://research.google/blog/unlocking-7b-language-models-in-your-browser-a-deep-dive-with-google-ai-edges-mediapipe/ . Yet another example is the model 33 GB , which https://huggingface.co/mlc-ai/Llama-3.1-70B-Instruct-q3f16 1-MLC/tree/main Llama-3.1-70B-Instruct-q3f16 1-MLC likewise runs in the browser https://chat.webllm.ai/ choose the "Llama 3.1 70B Instruct" model in the picker . Web applications that utilize large Wasm modules can store these modules using COS and access them across different origins. This enables efficient sharing of files between applications, reducing redundant downloading and improving performance. A notable example is Google's Flutter framework, which uses several Wasm files that are requested millions of times daily across thousands of hosts: Request https://gstatic.com/flutter-canvaskit/ | Size | Hosts | Requests | |---|---|---|---| 36335019a8eab588c3c2ea783c618d90505be233/chromium/canvaskit.wasm | a18df97ca57a249df5d8d68cd0820600223ce262/chromium/canvaskit.wasm 36335019a8eab588c3c2ea783c618d90505be233/canvaskit.wasm a18df97ca57a249df5d8d68cd0820600223ce262/canvaskit.wasm Source: Google-internal data from the Flutter team: "Flutter engine assets by unique hosts - one day - Dec 10, 2024". Traditionally, bundlers have combined vendor code and user code, leading to low cache hit rates even before the regular HTTP cache was isolated. By bundling vendor code separately and in its entirety for example, the complete React library instead of using dead-code elimination, developers can ensure a higher cache hit rate. Storing such files once with the COS API allows multiple web apps to share the same highly popular libraries. Web games built with game engines that have browser support such as Godot https://godotengine.org/ or Unity https://unity.com/ can store the core game engine code in COS and only load game-specific assets such as textures and game logic from the network. Web gaming portals such as WebGamer https://webgamer.io/ that host plenty of casual games with a short path to gameplay on different cross-origin iframes can benefit greatly from this. The COS API will be available through the navigator.crossOriginStorage interface. Files will be stored and retrieved based on their hashes, ensuring that each file is uniquely identified. - Hash the contents of the files using SHA-256 or an equivalent secure algorithm, see Appendix B appendixb-blob-hash-with-the-web-crypto-api . The hash algorithm used is communicated as a valid. HashAlgorithmIdentifier - Request a FileSystemFileHandle object for the file, specifying the file's hash. - Write the file's data to the FileSystemFileHandle object and store it in Cross-Origin Storage. When writableStream.write data is called, the user agent must verify that the hash of data matches the declared hash, using the algorithm specified in hash.algorithm . If the hashes do not match, the user agent must throw a NotAllowedError DOMException and must not store the data in COS. Note If hash.value is not a valid lowercase hexadecimal string of length 64, or hash.algorithm is not a valid HashAlgorithmIdentifier https://w3c.github.io/webcrypto/ dom-hashalgorithmidentifier , the user agent must throw a TypeError . / Example usage to store a single file. / // The hash of the desired file. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; // First, check if the file is already in COS. try { const handle = await navigator.crossOriginStorage.requestFileHandle hash ; // The file exists in COS. const fileBlob = await handle.getFile ; // Do something with the blob. console.log 'Retrieved', fileBlob ; return; } catch err { // If the file wasn't in COS, load it from the network and store it in COS. if err.name === 'NotFoundError' { // Load the file from the network. const fileBlob = await loadFileFromNetwork ; try { const handle = await navigator.crossOriginStorage.requestFileHandle hash, { create: true, // Optional: Only allow these origins to read the file. origins: 'https://example.com', 'https://example.org' , }, ; const writableStream = await handle.createWritable ; await writableStream.write fileBlob ; await writableStream.close ; } catch err { // The write failed. } return; } // 'NotAllowedError', the user agent did not grant access to the file. console.log 'The user agent did not grant access to the file.' ; } The origins field is useful for sharing resources between a set of related origins without making them globally available. This option is recommended for proprietary resources or resources for which global COS cache hits are not anticipated. For example, if a company has two related sites, write.example.com and calculate.example.com , that both use the same AI model for proofreading, they can store the model in COS and restrict access to just these two origins. This way, the model is not globally available to all sites that use COS, but only to the two related sites that need it. js // The hash of an AI model for proofreading. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; // Site write.example.com stores the model and restricts it to itself and // calculate.example.com . const handle = await navigator.crossOriginStorage.requestFileHandle hash, { create: true, origins: 'https://calculate.example.com', 'https://write.example.com' , } ; // Write the file… // Now, calculate.example.com can request the same hash and it will be found. // Any other origin NOT in the list e.g., https://unrelated.com will receive // a NotFoundError when requesting this hash, even if it's stored in COS. By specifying origins: ' ' when storing a file, the file becomes globally available to all origins that use COS. This option is appropriate for widely used resources that many sites are likely to share, such as popular AI models, Wasm modules, or JavaScript libraries. This is an explicit opt-in to avoid developers accidentally making resources globally available, which could lead to cross-site leaks. js // The hash of a very common AI model. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; const handle = await navigator.crossOriginStorage.requestFileHandle hash, { create: true, origins: ' ', } ; // Write the file… // Now, any origin can request the same hash and it will be found. By omitting the origins option altogether when storing a file, the file becomes available only to Same-Site origins that use COS. This is a good option for resources that are expected to be shared across multiple subdomains of the same site, but not across completely unrelated sites. js // The hash of a company's proprietary AI model. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; const handle = await navigator.crossOriginStorage.requestFileHandle hash, { create: true, } ; // Write the file… // Now, any Same-Site origin can request the same hash and it will be found. The visibility of a resource in COS can be upgraded but never downgraded: Restricted to more permissive : If a resource was initially stored with an origins list, any site including the original storer or a completely different site can later call requestFileHandle for the same hash with create: true and change the origins field to a more permissive value. If the user agent verifies the hash matches, the resource is then marked as available according to the new origins value. The new site must still write the full file using the returned FileSystemFileHandle object, to prevent sites from using this behavior to detect whether a file was previously stored. Permissive to more restricted : If a resource is already permissively available in COS, any attempt to store it again with a more restrictive origins list is ignored. The resource remains globally available, and the user agent should log a warning to the console to inform the developer that the restriction was not applied. To store or retrieve multiple files, call requestFileHandle once per file and combine with Promise.all for concurrent requests: / Example usage to store multiple files. / // The hashes of the desired files. const hashes = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }, { algorithm: 'SHA-256', value: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', }, ; // First, check if the files are already in COS. try { const handles = await Promise.all hashes.map hash = navigator.crossOriginStorage.requestFileHandle hash ; // All files found in COS. for const handle of handles { const fileBlob = await handle.getFile ; // Do something with the blob. console.log 'Retrieved', fileBlob ; } return; } catch err { // At least one file wasn't found — fetch all from the network and store them. if err.name === 'NotFoundError' { try { const fileBlobs = await loadFilesFromNetwork ; const handles = await Promise.all hashes.map hash = navigator.crossOriginStorage.requestFileHandle hash, { create: true } ; for let i = 0; i < handles.length; i++ { const writableStream = await handles i .createWritable ; await writableStream.write fileBlobs i ; await writableStream.close ; } } catch err { // The write failed. } return; } // 'NotAllowedError', the user agent did not grant access to the file. console.log 'The user agent did not grant access to the file.' ; } - Request a FileSystemFileHandle object for the file, specifying the file's hash. - Check if the resource exists in COS and make sure it can be shared without causing privacy issues. - Retrieve the FileSystemFileHandle object after the user agent has granted access. Note A NotFoundError DOMException does not necessarily mean the file is absent from COS. User agents may suppress availability of a file for privacy reasons see Availability gating availability-gating . Callers should handle NotFoundError by falling back to a network fetch, regardless of the cause. / Example usage to retrieve a single file. / // The hash of the desired file. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; try { const handle = await navigator.crossOriginStorage.requestFileHandle hash ; // The file exists in COS. const fileBlob = await handle.getFile ; console.log 'Retrieved file', fileBlob ; // Do something with the blob. } catch err { if err.name === 'NotFoundError' { // Load the file from the network. const fileBlob = await loadFileFromNetwork ; // Return the file as a Blob. console.log 'Obtained file from network', fileBlob ; return; } // 'NotAllowedError', the user agent did not grant access to the file. console.log 'The user agent did not grant access to the file.' ; } / Example usage to retrieve multiple files. / // The hashes of the desired files. const hashes = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }, { algorithm: 'SHA-256', value: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', }, ; try { const handles = await Promise.all hashes.map hash = navigator.crossOriginStorage.requestFileHandle hash ; // All files found in COS. for const handle of handles { const fileBlob = await handle.getFile ; // Do something with the blob. console.log 'Retrieved file', fileBlob ; } } catch err { if err.name === 'NotFoundError' { // Load the files from the network. const fileBlobs = await loadFilesFromNetwork ; // Do something with the blobs. console.log 'Obtained files from network', fileBlobs ; return; } // 'NotAllowedError', the user agent did not grant access to the files. console.log 'The user agent did not grant access to the files.' ; } To illustrate the capabilities of the COS API, consider the following example where two unrelated sites want to interact with the same common large language model. The first site stores the model in COS and makes it globally available, while the second site retrieves it. On Site A, a web application stores a large language model in COS. js // The hash of the desired file. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; try { const handle = await navigator.crossOriginStorage.requestFileHandle hash ; // Use the file and return. // … return; } catch err { if err.name === 'NotFoundError' { // Load the file from the network. const fileBlob = await loadFileFromNetwork ; // Compute the control hash using the method in Appendix B. const controlHash = await getBlobHash fileBlob ; // Check if control hash and known hash are the same. if controlHash == hash.value { // Downloaded file and desired file are different. // … return; } try { const handle = await navigator.crossOriginStorage.requestFileHandle hash, { create: true, origins: ' ', // Make the file globally available. }, ; const writableStream = await handle.createWritable ; await writableStream.write fileBlob ; await writableStream.close ; console.log 'File stored.' ; } catch err { // The write failed. } return; } // 'NotAllowedError', the user agent did not grant access to the file. console.log 'The user agent did not grant access to the file.' ; } On Site B, entirely unrelated to Site A, a different web application retrieves the same popular model from COS. js // The hash of the desired file. const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; try { const handle = await navigator.crossOriginStorage.requestFileHandle hash ; const fileBlob = await handle.getFile ; console.log 'File retrieved', fileBlob ; // Use the fileBlob as needed. } catch err { if err.name === 'NotFoundError' { // The file wasn't in COS. console.error err.name, err.message ; return; } // 'NotAllowedError', the user agent did not grant access to the file. console.log 'The user agent did not grant access to the file.' ; } Unrelated sites: The two sites belong to different origins and do not share any context, ensuring the example demonstrates cross-origin capabilities. Strictly opt-in: Site A explicitly opts in to make the file globally available by setting origins: ' ' when storing the file. This ensures that the file is not accidentally made available to all sites. Cross-origin sharing: Despite the different origins, the files are securely identified by their hashes, demonstrating the API's ability to facilitate cross-origin file storage and retrieval. The current hashing algorithm is SHA-256 https://w3c.github.io/webcrypto/ alg-sha-256 , implemented by the Web Crypto API . If hashing best practices should change, COS will reflect the implementers' recommendation https://w3c.github.io/webcrypto/ algorithm-recommendations-implementers in the Web Crypto API. The hashing algorithm used is encoded in the hash object's algorithm field as a HashAlgorithmIdentifier https://w3c.github.io/webcrypto/ dom-hashalgorithmidentifier . This flexible design allows changing the hashing algorithm in the future. The hash string must be a valid lowercase hexadecimal string of length 64 for SHA-256 . The algorithm field must be a valid , e.g. https://w3c.github.io/webcrypto/ dom-hashalgorithmidentifier HashAlgorithmIdentifier "SHA-256" . js const hash = { algorithm: 'SHA-256', value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4', }; In the context of evaluating carbon emissions in digital data usage https://websitesustainability.com/cache/files/research23.pdf , current methodologies predominantly utilize a kilowatt-hour kWh per gigabyte GB framework https://sustainablewebdesign.org/estimating-digital-emissions/ to estimate the operational energy intensity of data transmission and storage. This approach provides the following energy consumption benchmarks: Network transmission: 0.013 kWh/GB User devices: 0.081 kWh/GB While this document does not aim to critically assess the precision of these estimates, it is an established principle that minimizing redundant data downloads and storage is inherently beneficial for sustainability. The Ethical Web Principles https://w3ctag.github.io/ethical-web-principles/ specifically highlight that the Web "is an environmentally sustainable platform" https://w3ctag.github.io/ethical-web-principles/ sustainable and suggest "lowering carbon emissions by minimizing data storage and processing requirements" as measures to achieve this. Consequently, one of the key objectives of the COS API is to enhance Web sustainability by reducing redundant large file downloads when such files are possibly already stored locally on the user's device. Important In the context of AI, its implications for sustainability efforts are undeniable. It's essential to adhere to Web Sustainability Guidelines https://w3c.github.io/sustainableweb-wsg/ when integrating AI solutions. Prior to implementing AI, it's recommended to assess and research visitor needs https://w3c.github.io/sustainableweb-wsg/ assess-and-research-visitor-needs to ensure that AI is a justifiable and effective solution that truly improves the experience. For example, by increasing user privacy of video calls by applying AI-based background blurring. What should happen if two tabs depend on the same file, check COS, see the file is not in COS, and start downloading? Should this be handled more efficiently? How often does this happen in practice? This proposal does not address this case. In the worst case, the file is downloaded twice but stored only once in COS, which is considered an acceptable outcome. Because requestFileHandle operates on one file at a time, there is no concept of a partial batch result. Each call either resolves with a handle or throws a NotFoundError DOMException for that specific file. This makes it straightforward for developers to check which files are present and which are missing, and to handle each case independently—for example, by only fetching the missing files from the network. js const results = await Promise.allSettled hashes.map hash = navigator.crossOriginStorage.requestFileHandle hash ; const missing = hashes.filter , i = results i .status === 'rejected' ; // Fetch only the missing files from the network. Note that a NotFoundError does not necessarily mean the file is absent from COS. User agents may return NotFoundError for privacy reasons see Availability gating availability-gating , making the response indistinguishable from genuine absence. Should there be a required minimum file size for a file to be eligible for COS? No minimum file size is proposed. It would be trivial to inflate a file's size to meet any such threshold, for example by appending padding bytes or comments. Under critical storage pressure, user agents could offer a dialog that invites the user to manually free up storage. The user agent could also delete files automatically based on, for example, a least recently used approach. User agents are further expected to provide settings UI through which users can inspect which files are stored in COS and which origins have most or least recently accessed each file. Users may then choose to delete files from COS through this UI. When the user clears site data, all usage information associated with the origin should be removed from files in COS. If a file in COS, after the removal of usage information, is deemed unused, the user agent may delete it from COS. If a user already has manually downloaded a file such as a large AI model, should the user agent offer a way to let the user put the file in COS? This could be an affordance provided by the user agent. To facilitate manual COS management, one approach would be to allow developers to store a human-readable description alongside the resource. Apps could reference to the same file identified by a unique hash using different descriptions. For example, an English site could refer to the g-2b-it-gpu-int4.bin https://storage.googleapis.com/jmstore/kaggleweb/grader/g-2b-it-gpu-int4.bin AI model as "Gemma AI model from Google", whereas another Spanish site could refer to it as "modelo de IA grande de Google". Instead, we envision user agents to enrich COS management UI based on the hashes. For example, a user agent could know that a file identified by a given hash is a well-known AI model and optionally surface this information to the user in the user agent settings UI. Storing files by their names rather than using hashes would risk name collisions, especially in a cross-origin environment. The use of hashes guarantees unique identification of each file, ensuring that the contents are consistently recognized and retrieved. Storing files based on their URLs would work if apps reference the same URLs, for example, on the same CDN, but wouldn't work if apps reference the same file stored at different locations. Different origins can manually open the same file on disk, either using the File System Access API's showOpenFilePicker method or using the classic