This page is the threat model for a security reviewer. Each claim names the file and function that make it true, and says whether a test covers it. A claim read from the code with no test says so. The wire formats are in protocol.md.
The page covers these parties and places:
/.spool/* endpoints, who needs no credentialsIt does not cover a compromised server, a compromised browser, or malware that reads the device's disk.
The worker keeps these stores. Sealed means AES-GCM ciphertext at rest, described under Sealed Stores.
| Store | Where | Holds | Sealed | Removed by |
|---|---|---|---|---|
| Public replica | IndexedDB spool: artifacts, records, projections, meta |
public artifacts, records, projections, tag versions, the cursor, the site's security headers | no | Spool-Clear: *, a reset sync, a runtime version change |
| Principal partition | IndexedDB spool-p-<partition id> |
principal artifacts, records, projections, replica meta (with the security headers of principal paths) | yes | a logout path, Spool-Clear, a change of partition, session-ended |
| Persistent keys | IndexedDB spool-keys, store keys |
one non-extractable key per partition with persistent retention |
n/a | with its partition |
| Current partition | IndexedDB spool-keys, store meta |
the id of the partition in use | no | with its partition |
| Session keys | worker memory | one key per partition with session retention |
n/a | with its partition; also lost when the browser stops the worker |
| Beacon queue | IndexedDB spool, store beacons |
held analytics requests, view events, loads reports, flush clocks | no | a flush, or the age cap. A logout sends or drops the same-origin ones |
| Cache storage | spool-static-<version>, spool-assets-<version>, spool-third |
static files and the manifest, immutable same-origin assets, allowed third-party files | no | caches of older versions on activation. Not by a logout |
The device holds only what the server already sent that principal. The worker has four sources of artifact content: /.spool/artifact, the artifacts list in a beacon answer, the catalogue list in a beacon answer, and a response it fetched itself for a static route. The artifacts list is built without an identity, so it holds public artifacts only, and accept (src/sw/runtime.ts) discards any artifact whose scope is not public. The catalogue list is built with the identity of the beacon request, and acceptCatalogue keeps a principal entry only when the answer's Spool-Partition equals the partition the worker holds. A principal artifact from /.spool/artifact is stored only when the response's Spool-Partition equals the partition the worker holds (provision, src/sw/runtime.ts). tests/node/partitions.spec.ts covers an artifact answered for another partition, and tests/node/catalogue.spec.ts covers a catalogue answered for another one.
A route with persist: false keeps its artifact in worker memory and writes nothing to IndexedDB.
Authorisation runs on the server. buildArtifact (src/server/spool.ts) resolves the identity from the request, answers 401 for a principal route with no identity before it runs depends or load, and passes ctx.identity to load. Only what load returns reaches the device. decorate writes Spool-Artifact for a principal route only when the identity resolves. tests/unit/server.spec.ts covers the 401 and the header rule.
The worker authorises nothing. It decides whether to show a stored page from the partition it holds and the route's freshness bound. It cannot show a visitor data the server never sent that partition. It can show a page the server sent earlier after the server's view of the visitor's permissions has changed, for as long as Revocation describes.
Four paths answer anyone.
| Path | What a caller learns or can do |
|---|---|
/spool-sw.js |
the full worker config: every route's pattern, scope, freshness and retention, the compiled templates, beacon and logout paths, collector entries, the third-party allowlist, provisioning priors and the client tag |
/.spool/sync |
with from=0, every tag that has changed since the vector began, each with its version. Tag names and the write rate are public. With an identity option the answer also carries the caller's own partition id in Spool-Partition |
/.spool/artifact?path=... |
the artifact of any public route. Each call runs depends and load, and the answer is no-store |
/.spool/beacon |
accepts a batch from anyone. parseBeaconBatch checks structure, never truth, so a sink receives forged events. A batch's want makes the server build up to 8 public artifacts per request, and its catalogue makes the server run the named route's catalogue function and build up to 32 pages (MAX_CATALOGUE_BUILD) |
A template is code that ships to every visitor, so it must hold nothing private. Page data is what carries private values, and it travels only in an authorised artifact.
The server sets no CORS headers on /.spool/*, so a page on another origin cannot read these answers. It can still cause the requests. respondBeacon reads the body as text and does not check Content-Type, so another site can post a batch with a request that needs no preflight. Nothing rate-limits any of these paths. Treat sink data as untrusted input, and put a rate limit in front of the paths if build cost or forged events matter.
The error text of a ProtocolError that route code throws reaches the caller in a 400 (answer, src/server/spool.ts). Any other error answers internal error, and tests/unit/server.spec.ts checks that a thrown message does not leak.
A route with scope: 'principal' is stored in a partition per principal. The partition id is an HMAC-SHA-256 under the server secret over spool-partition:1, a NUL byte and the principal, encoded as 43 base64url characters (partitionId, src/server/identity.ts). The browser can tell partitions apart and learns nothing about the principal from the id. A different secret gives a different id for the same principal, and tests/unit/server.spec.ts checks that 500 principals get 500 ids.
spool-p-<id> and is stored in clear in spool-keys. A reader of the profile sees that a partition exists. Testing a guessed principal against an id needs the secret.secret is a string or a Uint8Array, and the code enforces no minimum length. Use a long random value and keep it out of the client.The worker swaps the principal store under a lock (serial, src/sw/runtime.ts), so two responses never swap it at once. SealedStorage.kill turns writes into no-ops and reads into empty answers once a partition is shut, so a request that outlives a wipe cannot bring the store back. tests/node/partitions.spec.ts covers a write after the store is shut and a request that outlives a logout.
sealedStorage (src/sw/seal.ts) wraps a partition's storage.
crypto.subtle.generateKey with extractable: false, a fresh random 12-byte IV for each value, and additional data of <partition>, a NUL byte, <kind>, a NUL byte and <id>. The kinds are artifact, record, projection and meta.tests/node/partitions.spec.ts reads the rows and checks that each holds only its key fields and ciphertext.retention picks the key. The default is session.
| Retention | Key | After a worker restart |
|---|---|---|
persistent |
a non-extractable CryptoKey in IndexedDB spool-keys, per partition (idbVault, src/sw/partition.ts) |
readable. Survives a worker restart and a browser restart |
session |
a CryptoKey in a Map in worker memory |
lost. The sealed rows read as absent, and getArtifact deletes a row it cannot open |
An artifact takes the retention of its route. Records, projections and the meta belong to no one route and use persistent when any principal route is persistent, and session otherwise. tests/node/partitions.spec.ts covers a restart with one route of each kind.
Deleting a key makes what it sealed unreadable. That is crypto-shredding, and destroy runs it in this order: shut the store, delete the keys, delete the database (idbPartitions, src/sw/partition.ts).
The seal is not a defence against these:
persistent retention. The key and the ciphertext live in the same profile. extractable: false stops script from exporting the key. Whether a browser writes the raw key bytes into its profile files is the browser's choice, and spool has not tested it. Do not rely on persistent against disk forensics.session key sits in worker memory and the data is readable by the worker.The public replica, the beacon queue and Cache storage are not sealed.
Any script that runs on the site's origin can use whatever the page can. Four ways, read from the code and not covered by a test:
navigate, src/sw/runtime.ts) with the frame and content-security headers the network answer carried, so X-Frame-Options and frame-ancestors apply to it as they do to a network view (see Content Security Policy).fetch a principal data route. dataFile answers any GET for such a route from the stored value.spool:partition, spool:graph and spool:status to the worker and read the ids it holds. The replies list ids and counts, never content (message, src/sw/runtime.ts).Escaping is part of rendering. The template interpreter HTML-escapes every {{field}}. {{{field}}} prints raw, and defineRoute refuses it for a field not listed in trusted (compileTemplate, src/protocol/template/compile.ts). Assembly fragments and slots are inserted by string replacement with no escaping, because they are markup from the server (assemble, src/protocol/assembly.ts), so a slot built from visitor input must be escaped in load. A projection route renders through the site's own function, and escaping there is that function's job.
SwConfig.logout lists patterns. A same-origin request whose path matches one, with any method, takes the worker through sendSameOrigin and then wipePartition('logout') before the request is sent (run, src/sw/runtime.ts). The sequence:
serial).destroy: shut the store (kill), delete the keys (shred), delete the IndexedDB database.{ type: 'spool:cleared', reason: 'logout' } to every page of the origin.Step 1 is the only step that uses the network. A held same-origin beacon is replayed with the browser's cookies (credentials: 'same-origin'), so one held from the user who is signing out would reach the server under whoever signs in next. sendSameOrigin (src/sw/analytics.ts) takes the held same-origin requests that are not spool's own events out of the queue and replays them while the old session is still the browser's. A request that fails, or that takes longer than LOGOUT_SEND_MS (3 seconds), is dropped and counted under the vendor same-origin in the /.spool/dropped record, and after the first thrown error the rest are dropped without a try. Nothing is replayed under another session. Held cross-origin events carry no cookies and keep their ordinary flush. The wipe itself does not wait for the network, so it completes with the network down, and tests/node/partitions.spec.ts checks the logout offline and with the network up.
Only a logout path takes this step. A change of partition or a Spool-Clear arrives in a response, after the browser already holds the new session's cookies, so a same-origin beacon held before it can still replay under the new session. One worker serves every tab, so one wipe empties the principal store for all of them. Each page receives the broadcast and the page runtime raises a spool:cleared event on window. A page has to blank what it shows, because spool does not touch the DOM.
A Spool-Clear header, or a navigation answered without Spool-Partition, takes the same path (early, src/sw/runtime.ts). It runs before the response is handed back, and a logout answer, which still names the identity it answered, cannot bring the partition back. tests/node/partitions.spec.ts covers both.
The worker reads Spool-Clear on its own (decodeEnvelopeOrClear, src/protocol/envelope.ts). When another spool header on the response is malformed, the worker drops every other field and still applies a valid Spool-Clear. A Spool-Clear that is itself malformed counts as *, so a bad value wipes and never keeps data. A response that names another protocol version is left alone. tests/unit/envelope.spec.ts and tests/node/partitions.spec.ts cover both cases.
Left in place by a logout:
A logout pattern matches on the path alone. A GET to it, such as a link or a cross-site top-level navigation to that URL, wipes the local copy of the principal store. It leaves the server session alone unless the application's own logout handler ends it on GET.
A sign-out the worker never sees, such as a session that expires on the server or a sign-out in another browser, leaves the store in place. See Revocation and The Open Window.
Spool has no model of permissions. The application tags a page with the permission it needs, for example perm:<principal>, and calls spool.invalidate when the permission goes. tests/node/partitions.spec.ts covers the result on a fake clock:
maxStale, the page shows with no request, even though the permission is gone. The default for a principal route is 300 seconds.maxStale, a hard route runs a required sync with need, which names the page's tags. The tag moved, the artifact is dropped, and the network answers. That is the first required sync.The consequences, each read from decide in src/sw/runtime.ts:
maxStale from the page's last validation, plus any time the visitor does not view the page. Nothing pushes to a device. A device that never returns holds its data until it does, or until it is wiped.offline: 'serve' on a hard principal route, a device that is offline past the bound shows the stored page. The default refuse sends the view to the network, which fails.soft principal route shows the stored page first and syncs after, so it shows one stale view past the bound.A catalogue puts the data of pages the visitor has not opened on the device (protocol.md). The entry is data the server would send on a visit, built by the route's load with the identity of the beacon request, so it holds nothing the visitor may not see. Five things follow from the visitor never having asked for it.
spool:graph and spool:partition, and can fetch a principal data entry once the route serves it.Replica.sweep), and the first one after a tag moves drops the entry. When those ids and the page's own are more than MAX_NEED, the worker makes a full sync by cursor. tests/node/catalogue.spec.ts covers the drop, the full sync, and the control where nothing was revoked.maxStale check like any page, so a permission revoked within the bound still shows it. A device that makes no required principal sync keeps the entry until the visitor returns or the partition is wiped, as Revocation says of any page.sweep: true, and createSpool skips principal routes when it does not. A second worker implementation that does not sweep must not send it. A request that resolves to nobody gets none, and the worker keeps a principal entry only for the partition it holds.Save-Data: on from the beacon request. A proxy that strips the header, or a server that never sees it, sends a catalogue to a visitor who asked the browser to save data.The catalogue request names the path of the page the visitor is on. A page the worker showed locally made no request and spool.view never carries a path, so this request is the one place the server learns the path of a page it did not serve. The worker sends it only for routes that declare catalogue, once per path while it runs, with a flush that leaves anyway. A site that does not want the server to learn local views leaves catalogue off.
A catalogue request costs the server a call to the route's catalogue and up to MAX_CATALOGUE_BUILD page builds, with no credentials and no rate limit. The route's catalogue receives a context whose request is a GET carrying the headers of the beacon request, cookies included. Sweep tags go in the need query like any other tag name, so use opaque ids for anything sensitive (Sync Tag Names).
The worker learns who is signed in only from Spool-Partition on a response that reaches it. A local view makes no request, so it learns nothing. Between a sign-in and the first response that names the new partition, a local view of a principal route reads the store of the previous partition.
It happens in two cases:
spool.handle and decorate, or on another origin.decorate resolves the identity from the request, and the request that signs a visitor in, or switches accounts, still carries the identity from before./.spool/sync names the identity it sees. When createSpool has an identity option, answer (src/server/spool.ts) resolves the request's identity and respondSync (src/server/sync.ts) sends Spool-Partition for it, or no partition when the request resolves to nobody. fetchSync (src/sw/runtime.ts) reads the answer's headers through early before it uses the sync result. A different partition swaps the principal store and destroys the previous one, and no partition wipes the principal store (session-ended), the same as on a navigation. A hard principal route runs a required sync once its page is past maxStale, so the previous visitor's page is not shown after that sync: the swap or the wipe happens first, the page is gone from the store, and the view goes to the network.
The window therefore ends no later than the route's maxStale after the page was last validated, plus the next view of a principal page. The default for a principal route is 300 seconds. Inside maxStale the stored page still shows, and a sign-in elsewhere shows the previous visitor's page until the bound passes. Two cases reach the sync later. A soft principal route shows the stored page for the view that finds the bound passed and syncs after it, so that one view shows the previous visitor's page, and with sync: 'deferred' the sync waits for a network moment. A sync that fails counts as offline, and offline: 'serve' then shows the stored page. tests/node/partitions.spec.ts covers a sign-in and a sign-out the worker never saw, on a fake clock. The sync carries a partition only when the server has the identity option, and a worker holds a principal partition only from such a server.
The application can close the window sooner. The sign-in and account-switch handlers set the new value on their own response:
headers.set('spool-partition', await spool.partition(user));
early applies a differing Spool-Partition before it returns the response, and switchTo destroys the previous partition before then (src/sw/runtime.ts). A navigation answered with spool headers and no partition, such as a sign-out, wipes the store for the same reason. The mitigation does not cover a sign-in that never passes through the handler, and the required sync is what ends the window for one.
The proxy cannot read the application's session, so it reads cookies (decline and setsPrivateCookie, src/proxy/eligible.ts; handle, src/proxy/proxy.ts).
publicCookies, or with an Authorization header, is ineligible on a public route. The proxy forwards it untouched. When the answer is a 200 page it also adds Spool-Invalidate for spool:artifact:<path>, so the worker drops its copy of that path. A query-string request is forwarded with no drop.publicCookies carries Spool-Clear: *, whatever the method or status. That wipes the principal store and the public replica on the device that receives it. The check reads the cookie name only, so a Set-Cookie that deletes a cookie counts too, which makes a logout clear. tests/node/proxy.spec.ts covers the clear and the allowlist.publicCookies is a statement that a cookie carries no identity, such as a bot-check or load-balancer cookie. Listing a session or login cookie there removes the protection. A login no longer clears, and a signed-in request is no longer ineligible.createSpool does not inspect Set-Cookie.
Tag names are readable. A tag appears unsealed in the need query of a sync request, in the changed map of a full sync, in Spool-Artifact and Spool-Invalidate headers, in the deps of an artifact and in the have query of an artifact request. GET /.spool/sync?from=0 returns every tag that ever changed to any caller. A tag such as grades:alice or perm:alice tells an observer that the principal exists and when their data changes. The sealed store protects tags at rest and does nothing for them on the wire or on the server.
Use opaque ids for anything sensitive, for example a random id or an HMAC of the principal. Record and projection ids are IndexedDB keys, so they sit in clear at rest too. A principal route syncs with need, which names only the tags that page reads and not the whole diff. The cursor also shows the site's global write rate.
By default the server serves the page runtime itself at /.spool/<version>/client.js with Cache-Control: public, max-age=31536000, immutable. That path suits script-src 'self' and trusts no host beyond the site's own (tests/unit/server.spec.ts).
With cdn: { integrity } the injected tag is <script src="https://cdn.drupflare.com/spool/<version>/client.js" integrity="sha384-..." crossorigin="anonymous" defer data-spool-client>. The browser refuses a file whose sha384 differs. The hash is in the release notes and in dist/cdn/manifest.json, schema 2 (scripts/cdn-manifest.ts).
Access-Control-Allow-Origin on the file. The repo does not set it. verifyRemote (scripts/publish-cdn.ts) reads the file back after a publish with an Origin header, fails unless the header is * or the probe's origin, and tests/node/cdn.spec.ts covers that check.publish refuses to overwrite one (remoteExists, tests/node/cdn.spec.ts). Each commit to master also gets one under its nightly version, spool/<version>-<commit>/, published before the nightly package that points at it. Dev keys under spool/dev-<branch> are overwritten on every push and cached for five minutes (DEV_CACHE). Never pin a dev URL.importScripts('https://cdn.drupflare.com/spool/<version>/spool-sw.js') has no integrity check. It trusts the CDN host with the worker, and a worker can answer any request of the origin, which is more than a page script can do. Serve spool-sw.js from the site's own storage to avoid that trust.src/server/embedded.ts holds the worker core and page runtime as generated text. tests/node/embedded.spec.ts fails when it drifts from the source.Spool writes no Content-Security-Policy and configures none. decorate adds no policy. The worker reads the security headers of network answers and replays them on local renders, described below.
nonce. A policy that allows only nonce or hash scripts blocks the tag. The page then works as it did before, with no worker. The default same-origin path needs 'self', and the CDN form needs the CDN origin in script-src.data routes) is answered with the site's security headers and a Content-Type of the worker's own (navigate and dataFile, src/sw/runtime.ts). The headers are Content-Security-Policy, Content-Security-Policy-Report-Only, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy (POLICY_HEADERS, src/sw/policy.ts). No other header of the network answer is replayed, Cache-Control and Set-Cookie included.200 HTML navigation to a route's path, with no query string and no redirect, and from a 200 data read of its path (keepPolicy, src/sw/runtime.ts). Any 200 same-origin HTML navigation also replaces the site-wide set. A local render replays the path's own set, and a path with none of its own takes the site-wide set, which is the latest one seen. A set can be empty: a site that sends none is rendered locally with none.decide returns the reason refused and the view goes to the network, which stores the sets for the next one. The worker never serves a page without the policy the site sent with it. A view in shadow mode is not affected.keepPolicy, src/sw/replica.ts): the site-wide set and the sets of public paths in the public replica, and the set of a principal path in the sealed meta of its partition. Each replica keeps the sets of its 128 most recent paths (MAX_POLICY_PATHS). They go when the store does: Spool-Clear: *, a reset sync and a runtime version change empty the public replica, and a partition's sets go with its partition. A worker restart keeps them.tests/node/strategies.spec.ts covers the capture, the fallback, the refusal, a restart and the wipe, tests/node/partitions.spec.ts covers a principal page, and tests/browser/security.pw.ts checks in Chromium, Firefox and WebKit that a local view carries X-Frame-Options: DENY and a policy that forbids inline script, and that an inline script does not run. A <meta> policy is no longer needed for a local view.'self' or hashes.static route replays the headers of the response it stored, minus the transfer headers (replayHeaders, src/sw/static.ts), so a stored file keeps its Content-Security-Policy. tests/node/static.spec.ts checks it.worker-src or child-src.decorate(request, response, { hardReset: true }) adds Clear-Site-Data: "storage". It asks the browser to reset everything the origin holds, the service worker included. Safari's support is unverified, so rely on the worker's own wipe. The worker wipes before the logout request leaves. The header acts only after a response arrives, so it needs the network. After the header lands, the next views are plain network views until the page registers the worker again.
A trimmed stylesheet is a correctness risk and not a confidentiality one. The proxy only removes rules from a file that every visitor already receives, and the manifest holds public stylesheets and the words of public pages and scripts. Nothing a visitor sends goes into it. A wrong trim shows a page with a rule missing. It leaks nothing and it changes no access decision.
Two checks stand between a stale or incomplete manifest and a visitor (src/proxy/proxy.ts, src/trim/manifest.ts).
source. A deploy that changes the file makes the manifest wrong for it, so the proxy serves the origin's bytes and counts a mismatch until the manifest is rebuilt.spool-trim=0. This covers pages the crawl never saw, including pages the proxy does not keep, such as a query string or a signed-in page.tests/node/proxy.spec.ts and tests/browser/trim.pw.ts cover both. What the guard cannot see:
buildTrimManifest reads the words of the foreign scripts the crawl loaded, so it covers what they did on the day of the crawl. The guard never reads those URLs, so a vendor that ships a new class later is not caught until the manifest is rebuilt.'btn-' + kind, where no whole class name appears in the HTML or a script.@import inside another stylesheet. It is not linked from the page, so the guard cannot send it a spool-trim=0, and a manifest built from linked files holds no entry for it.A site that cannot accept these limits should not pass trim. The same saving is available from a build step that purges with a safelist, which puts the same decision in the site's hands.
POST /.spool/purge exists in proxy mode only (respondPurge, src/proxy/purge.ts).
purgeSecret, every call answers 403, so a proxy that never configured purges cannot be made to bump its vector. tests/node/proxy.spec.ts covers it.Authorization: Bearer <secret>. equalSecrets runs both values through HMAC-SHA-256 under a random key made once per isolate, then XORs the two 32-byte digests over their whole length with no early exit. The secret's value and its length do not change the work. JavaScript offers no timing guarantee, and spool has not audited an engine for one, so the claim is limited to the removal of the data-dependent exit.wrangler secret put PURGE_SECRET on Workers, and call the endpoint over HTTPS only.A request listed in beacons or matched by a collectors entry is held in IndexedDB, in clear, until a network moment. The page gets an answer at once, 204 for a POST and a one-pixel GIF for a GET (collectorAnswer, src/sw/collect.ts), so the page is told the request succeeded before it has been sent. An event older than the collector's maxAgeMs is dropped, counted and reported as /.spool/dropped, and it is never sent late.
credentials: 'same-origin'). A cross-origin replay sends none (credentials: 'omit'), so identity has to be in the payload (replay, src/sw/runtime.ts).spool.view event names the route the visitor came from (from): the route pattern of a same-origin referrer, such as /node/:id, or external for another origin. It reveals which kind of page preceded this one and nothing else. It never holds the referrer's URL, path, query string or the external site's name, and a same-origin path that matches no declared route is left out. Set views: false to stop the event.thirdParty.allow lets the worker keep a GET response from a listed origin, for a readable (CORS) answer at a URL that looks versioned or that the origin marks immutable (versionClass, src/sw/thirdparty.ts). allowOpaque is off. The worker checks no hash of the file. A file kept as immutable is never checked again, so one bad answer at a hashed-looking URL stays until the entry is evicted (maxEntries, oldest first). A revalidate file is checked at a network moment once it is a day old.spool.view, spool.loads and the drop counts, which go to the sink. A vendor receives the body the page sent. Spool creates no vendor event. The page's immediate answer to a held request is the one thing it makes up, and it is stated above.collectors entry and no thirdParty.allow origin (classify, src/sw/router.ts), and the browser handles those as it always did. Spool has no ad-specific code and no check against an operator listing an ad host.spool.invalidate announces one.Each limitation is stated in the section it belongs to. This table lists them in one place.
| Limitation | Section |
|---|---|
A sign-in whose response the worker sees without the new Spool-Partition leaves the previous partition readable until the first required sync, which is no later than maxStale plus the next view |
The Open Window |
A revoked permission shows for up to maxStale, nothing pushes a revocation, and offline: 'serve' shows a page past the bound |
Revocation |
Tag names and the write rate are readable by anyone through /.spool/sync |
Sync Tag Names |
| A sealed row is bound to its place and not to a version, so a row can be rolled back | Sealed Stores |
persistent retention keeps the key in the same profile as the ciphertext |
Sealed Stores |
| The seal gives no protection against script on the origin | Script on the Origin |
| A policy with a nonce chosen for each response does not match markup the worker renders, so its inline scripts fail on a local view | Content Security Policy |
| Assembly slots and fragments are not escaped | Script on the Origin |
| A logout leaves the public replica, held cross-origin and spool events and Cache entries in place | Logout and the Wipe Order |
A change of partition or a Spool-Clear that is not a logout path does not send or drop held same-origin beacons, which replay under the new session |
Logout and the Wipe Order |
A logout pattern matches any method, so a GET wipes the local principal store |
Logout and the Wipe Order |
| Proxy mode cannot tell a rolling session cookie from a login, and a cookie set outside the proxy produces no clear | Cookies in Proxy Mode |
/.spool/beacon, /.spool/sync and /.spool/artifact need no credentials and have no rate limit, and a flush can ask the server to build 8 artifacts and a catalogue of up to 32 pages |
|
| A catalogue request names the path of the page the visitor is on, including a page the worker showed locally, which no other request or view event carries | Catalogue Entries |
| An unopened principal catalogue entry stays on the device until a required principal sync names its tags, the visitor returns, or the partition is wiped | Catalogue Entries |
| A trimmed stylesheet can lose a rule for a class that a later vendor script, late-injected markup or a runtime-built class name needs, which the guard cannot see | Trimmed Stylesheets |
| The purge endpoint has no rate limit or lockout, and any holder of the secret can bump any tag | Purge Endpoint |
The importScripts form of the worker has no integrity check |
Script Delivery |
Clear-Site-Data support in Safari is unverified |
Clear-Site-Data |
| The secrets, the partition secret and the purge secret, have no enforced minimum length | Partitions and the HMAC, Purge Endpoint |