DELETE leaves stale contextual cache mappings, causing deleted filter/form-data keys to be reused
Bug description
DELETE leaves stale contextual cache mappings, causing deleted filter/form-data keys to be reused
Bug description
The temporary-state DELETE paths for dashboard filter state and Explore form data remove the primary cache entry, but can leave the secondary contextual mapping pointing to the deleted key.
As a result, a subsequent POST from the same session/tab/context can reuse a key that was previously deleted.
This affects both:
DashboardFilterStateRestApiExploreFormDataRestApi
The behavior is present on current master and the same underlying logic is present in 6.1.0.
Root cause
Temporary state creation keeps a secondary mapping so the same session/tab/resource can reuse an existing generated key.
For dashboard filter state, the mapping is based on:
contextual_key = cache_key(
session.get("_id"),
tab_id,
resource_id,
)
The DELETE command attempts to remove the same mapping:
tab_id = cmd_params.tab_id
contextual_key = cache_key(
session.get("_id"),
tab_id,
resource_id,
)
cache_manager.filter_state_cache.delete(contextual_key)
However, the DELETE API creates the command parameters without a tab_id.
Effectively, the command receives:
cmd_params.tab_id is None
so it attempts to delete a mapping for:
<session>;None;<resource>
rather than the mapping originally created for, for example:
<session>;1;<resource>
The primary state entry is deleted successfully, so the DELETE request returns success, while the actual contextual mapping remains.
ExploreFormDataRestApi has the same mismatch: DeleteFormDataCommand uses cmd_params.tab_id to construct the contextual key, but the DELETE endpoint passes only key.
Why this matters
On the next POST from the same context, the create path reads the stale contextual mapping:
key = cache_manager.filter_state_cache.get(contextual_key)
if not key or not tab_id:
key = random_key()
Because the stale mapping still returns the previously deleted key and tab_id is present, no new random key is generated.
The deleted key is then used again for the newly created state.
The same reuse pattern exists in CreateFormDataCommand.
Reproduction
Using a dashboard with filter-state access:
- Create filter state with a tab id:
POST /api/v1/dashboard/<dashboard_id>/filter_state?tab_id=1
with a valid JSON-string value representing State A.
Record the returned key as K1.
- Delete it:
DELETE /api/v1/dashboard/<dashboard_id>/filter_state/K1
The request returns success.
- Verify the primary entry is gone:
GET /api/v1/dashboard/<dashboard_id>/filter_state/K1
This should return 404.
- Create another filter state using the same browser session, dashboard and
tab_id=1, this time withState B:
POST /api/v1/dashboard/<dashboard_id>/filter_state?tab_id=1
Actual behavior
The second POST can return:
K1
again.
The stale contextual mapping still points to the previously deleted key, so the create path reuses it and recreates the primary state entry under K1.
A subsequent GET of K1 can therefore return State B.
Expected behavior
A successful DELETE should invalidate both the primary entry and its contextual mapping.
After deletion, creating state again in the same context should generate a fresh key:
K2 != K1
Affected code
Dashboard filter state:
superset/commands/dashboard/filter_state/create.py
superset/commands/dashboard/filter_state/delete.py
superset/temporary_cache/api.py
Explore form data:
superset/commands/explore/form_data/create.py
superset/commands/explore/form_data/delete.py
superset/explore/form_data/api.py
Existing test gap
Current tests verify that DELETE returns successfully, but do not verify that the contextual mapping is removed.
A regression test could exercise:
create with tab_id=1
→ delete returned key
→ create again with the same context
→ assert second key != deleted key
The existing Explore command test is especially close to this scenario: it creates form data with tab_id=1, invokes DeleteFormDataCommand without a tab_id, and only asserts that deletion returns True.
Historical intent
PR #18576 introduced the contextual secondary mapping used for key reuse.
Its design explicitly states that the secondary mapping should be kept in sync with DELETE events.
The current API/command contract appears to prevent that synchronization because the DELETE routes do not supply the context required by their delete commands.
Suggested remediation direction
The DELETE path needs a reliable way to identify and remove the contextual mapping associated with the primary key.
Simply deleting the primary cache entry is insufficient because the surviving mapping can cause that key to be reused later.
Possible approaches include passing and preserving the necessary context on DELETE, or maintaining a reverse association that allows the contextual mapping to be invalidated from the primary key.
Environment
- Superset: current
master - Also present in the corresponding temporary-cache logic in Superset 6.1.0
- Python: Not applicable for source-level analysis
- Node: Not applicable
- Browser: Not applicable
Screenshots/recordings
No response
Superset version
6.1.0
Python version
I don't know
Node version
I don't know
Browser
Chrome
Additional context
No response
Checklist
- I have searched Superset docs and Slack and didn't find a solution to my problem.
- I have searched the GitHub issue tracker and didn't find a similar bug report.
- I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
Source: apache/superset