Cache-Control: no-store does not prevent disk cache entry when multiple headers are sent
Categories
(Core :: Networking: Cache, defect, P2)
Tracking
()
People
(Reporter: piipokun1703, Assigned: valentin)
Details
(4 keywords, Whiteboard: [necko-triaged][necko-priority-queue][adv-main143+][adv-esr140.3+])
Attachments
(6 files)
|
203.90 KB,
image/png
|
Details | |
|
113.64 KB,
image/png
|
Details | |
|
2.29 KB,
text/x-python
|
Details | |
|
48 bytes,
text/x-phabricator-request
|
Details | Review | |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-beta+
|
Details | Review |
|
48 bytes,
text/x-phabricator-request
|
phab-bot
:
approval-mozilla-esr140+
|
Details | Review |
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36
Steps to reproduce:
1.Start a local HTTP server using the attached server.py (Flask).
2.Navigate to http://localhost:8082/test7 in Firefox.
3.This endpoint responds with three separate Cache-Control headers:
ใปCache-Control: no-store
ใปCache-Control: public
ใปCache-Control: no-cache
4.Open about:cache.
5.Observe that an entry for /test7 appears in the disk cache.
6.This occurs despite no-store being present.
7.The response also includes a Set-Cookie: sessionid=test_token header.
firefox 141.0.2(64bit) Windows 11
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:141.0) Gecko/20100101 Firefox/141.0
Actual results:
Firefox appears to honor only the last Cache-Control header.
If no-cache follows no-store, no-store is ignored.
The response ends up being cached.
The cache entry persists across browser restarts.
Expected results:
According to [RFC 9111 (Section 5.2.2.5)]the no-store directive prohibits intentional storage in non-volatile storage (e.g., disk), and instructs caches to make a best-effort attempt to avoid retaining the information even in volatile memory (e.g., RAM).
Thus, when a server sends Cache-Control: no-store, the browser:
- MUST NOT store the response in disk cache (non-volatile).
- SHOULD avoid memory caching, or at least remove the response from memory promptly.
- MUST correctly process multiple
Cache-Controlheaders and not ignoreno-storeeven when combined with other directives.
Therefore, when multiple Cache-Control headers are present (e.g., no-store and no-cache), the browser should combine them appropriately and enforce the most restrictive directive โ which is no-store in this case.
Even when multiple Cache-Control headers are sent separately, the browser should merge them and respect the most restrictive directive, i.e., no-store.
| Reporter | ||
Comment 1•9 months ago
|
||
| Reporter | ||
Comment 2•9 months ago
|
||
| Reporter | ||
Comment 3•9 months ago
|
||
Updated•9 months ago
|
Updated•9 months ago
|
Updated•9 months ago
|
Comment 4•9 months ago
|
||
I believe this behavior is by design.
Regardless of whether the header includes no-store or no-cache, Firefox may still write the data to the cache, but it wonโt use the cached data when those directives are present.
Valentin, please correct me if Iโm wrong. Thanks.
| Assignee | ||
Comment 5•9 months ago
|
||
nsHttpResponseHead::ParseHeaderLine_locked parses each header as it comes in.
It then updates its own member variables based on that header.
For cache-control, it's important to reparse the merged version of the header
instead of the most recent one, otherwise we'll miss directives present in
previous headers.
Updated•9 months ago
|
| Assignee | ||
Comment 6•9 months ago
|
||
Actually, if no-store is present we should not save the cache entry to disk (memory is fine).
We don't end up using it later though, but the fact that we save it to disk is still slightly problematic for privacy.
Not sure if I'd consider this a security issue though.
| Assignee | ||
Updated•9 months ago
|
Comment 7•9 months ago
|
||
Regardless of whether the header includes no-store or no-cache, Firefox may still write the data to the cache, but it wonโt use the cached data when those directives are present.
That is wrong. no-store was created to be explicit because browsers and middle-boxes were "caching and not using" the no-cache ones. It's used for sensitive data that should never be written to disk or any permanent place
Local file-system disclosure is not super high on our threat model (except in Private Browsing) so the severity is low.
| Assignee | ||
Updated•9 months ago
|
| Assignee | ||
Comment 8•9 months ago
|
||
Updated•9 months ago
|
Comment 10•9 months ago
|
||
Updated•9 months ago
|
Comment 11•9 months ago
|
||
The patch landed in nightly and beta is affected.
:valentin, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- See https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift for documentation on how to request an uplift.
- If no, please set
status-firefox143towontfix.
For more information, please visit BugBot documentation.
| Assignee | ||
Comment 12•9 months ago
|
||
nsHttpResponseHead::ParseHeaderLine_locked parses each header as it comes in.
It then updates its own member variables based on that header.
For cache-control, it's important to reparse the merged version of the header
instead of the most recent one, otherwise we'll miss directives present in
previous headers.
Original Revision: https://phabricator.services.mozilla.com/D260461
Updated•9 months ago
|
Comment 13•9 months ago
|
||
firefox-beta Uplift Approval Request
- User impact if declined: Cache-control: no-store would not be respected if it followed by other cache-control headers (probably rare in practice)
- Code covered by automated testing: yes
- Fix verified in Nightly: yes
- Needs manual QE test: yes
- Steps to reproduce for manual QE testing: Manual testing is optional, but see comment 3 for a test server
- Risk associated with taking this patch: low
- Explanation of risk level: This patch makes sure that the cache control parses the merged header, not just the most recent received cache-control header.
- String changes made/needed: none
- Is Android affected?: yes
| Assignee | ||
Comment 14•9 months ago
|
||
(In reply to BugBot [:suhaib / :marco/ :calixte] from comment #11)
:valentin, is this bug important enough to require an uplift?
The impact on users is probably small, but I've requested uplift anyway for corectness.
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Comment 15•8 months ago
|
||
| uplift | ||
Updated•8 months ago
|
Updated•8 months ago
|
Comment 16•8 months ago
|
||
Reproduced the issue described in comment 0 using an old Nightly build from 2025-08-06.
Verified that:
- using latest Nightly 144.0a1 http://localhost:8082/test7 is not even in the list in
about:cache?storage=disk - using latest Beta 143 build from treeherder http://localhost:8082/test7 is listed in about:cache?storage=disk` but it has 0 date in it also there are two other entries (see bellow). The favicon.ico one is the only one displayed in Nightly as well.
http://localhost:8082/test7 | 0 bytes | 0 bytes | 0 | 2025-08-28 16:38:37 | No expiration time
http://localhost:8082/favicon.ico O^partitionKey=%28http%2Clocalhost%29, | 207 bytes | 0 bytes | 1 | 2025-08-28 16:38:09 | Expired Immediately
predictor-origin:http://localhost:8082/ | 0 bytes | 0 bytes | 2 | 2025-08-28 16:38:09 | No expiration time
I verified across platforms (Windows 11, macOS 13 and Ubuntu 22.04).
Not sure if the above entries are to be displayed in the Beta build or not even though except the favico one they have 0 data in it. Nightly only displays the favicon.ico one.
| Assignee | ||
Comment 17•8 months ago
|
||
The icon entry doesn't matter for this bug.
I think both the predictor and /test7 entry should go away if you set network.predictor.enabled to false
This is already the default for release and was changed to false in Bug 1979415.
Comment 18•8 months ago
|
||
(In reply to Valentin Gosu [:valentin] (he/him) from comment #17)
I think both the predictor and /test7 entry should go away if you set
network.predictor.enabledto false
This is already the default for release and was changed to false in Bug 1979415.
Indeed the entries are gone if setting network.predictor.enabled to false, thanks for the extra info.
Closing this as verified fixed for 144 and 143. Will also come back and verify once/if the fix goes into 140esr.
| Assignee | ||
Comment 19•8 months ago
|
||
nsHttpResponseHead::ParseHeaderLine_locked parses each header as it comes in.
It then updates its own member variables based on that header.
For cache-control, it's important to reparse the merged version of the header
instead of the most recent one, otherwise we'll miss directives present in
previous headers.
Original Revision: https://phabricator.services.mozilla.com/D260461
Updated•8 months ago
|
Comment 20•8 months ago
|
||
firefox-esr140 Uplift Approval Request
- User impact if declined: Cache-control: no-store would not be respected if it followed by other cache-control headers (probably rare in practice)
- Code covered by automated testing: yes
- Fix verified in Nightly: yes
- Needs manual QE test: yes
- Steps to reproduce for manual QE testing: Manual testing is optional, but see comment 3 for a test server and comment 17 for testing context
- Risk associated with taking this patch: low
- Explanation of risk level: This patch makes sure that the cache control parses the merged header, not just the most recent received cache-control header.
- String changes made/needed: none
- Is Android affected?: yes
Updated•8 months ago
|
Updated•8 months ago
|
Comment 21•8 months ago
|
||
| uplift | ||
Comment 22•8 months ago
|
||
Also verified as fixed using latest esr140 build from treeherder across platforms (Windows 11, macOS 13 and Ubuntu 22.04).
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Updated•1 month ago
|
Description
•