#44385·superset

DELETE leaves stale contextual cache mappings, causing deleted filter/form-data keys to be reused

Author: hunter3x3-techCreated Sep 17, 2026Updated Sep 17, 2026
Labels#bug

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:

  • DashboardFilterStateRestApi
  • ExploreFormDataRestApi

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:

  1. 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.

  1. Delete it:
DELETE /api/v1/dashboard/<dashboard_id>/filter_state/K1

The request returns success.

  1. Verify the primary entry is gone:
GET /api/v1/dashboard/<dashboard_id>/filter_state/K1

This should return 404.

  1. Create another filter state using the same browser session, dashboard and tab_id=1, this time with State 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.