Skip to content

fix: isolate Cache.Sandbox scan results per sandbox#40

Merged
MikaAK merged 2 commits intomainfrom
fix/sandbox-scan-isolation
Apr 13, 2026
Merged

fix: isolate Cache.Sandbox scan results per sandbox#40
MikaAK merged 2 commits intomainfrom
fix/sandbox-scan-isolation

Conversation

@MikaAK
Copy link
Copy Markdown
Owner

@MikaAK MikaAK commented Apr 13, 2026

Summary

  • Cache.Sandbox.scan/3 read every key from the shared sandbox Agent regardless of which sandbox registered it. With async: true tests sharing a cache module, keys written in sandbox A leaked into scan output in sandbox B — producing duplicates or missing entries depending on timing.
  • The scan/1 macro now passes the current sandbox id as a new :sandbox_prefix scan option, and Cache.Sandbox.scan/3 filters stored keys to only those starting with that prefix before the existing match/type filters run.
  • Non-sandbox mode is unchanged (maybe_sandbox_scan_opts/1 is a no-op), so the Redis adapter keeps its current behavior and ignores the unknown option.

Repro

Two async tests each writing packages to the same cache module and calling get_package_list() (which uses scan/0 to enumerate keys) would see each other's writes.

Test plan

  • New test CacheSandboxTest > scan only returns keys from the current sandbox verifies scans from two concurrently-registered sandboxes are isolated
  • Confirmed the new test fails on main and passes with this change
  • Full test suite: no new failures (13 pre-existing RedisJSON setup failures unchanged)

Previously scan/1 returned every key in the shared sandbox Agent,
including keys written by concurrent async tests registered to the same
cache module. This caused leakage between tests (duplicate results from
one sandbox, or keys from another sandbox appearing in scan output).

The scan macro now passes the current sandbox_id as a sandbox_prefix
option, and Cache.Sandbox.scan filters stored keys to only those
matching that prefix. Non-sandbox mode and the Redis adapter are
unaffected (the opt is only set when sandbox? is true).
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.46%. Comparing base (a088b4a) to head (e4e8605).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #40   +/-   ##
=======================================
  Coverage   83.46%   83.46%           
=======================================
  Files          22       22           
  Lines         617      617           
=======================================
  Hits          515      515           
  Misses        102      102           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Previously, per-sandbox isolation was layered on top of the sandbox Agent
by prefixing keys with the sandbox_id in Cache (maybe_sandbox_key) and
filtering scan results via a sandbox_prefix option threaded through
Cache.Redis. This spread sandbox concerns across three modules and still
leaked data through operations that bypassed the prefix path.

Cache.Sandbox now owns isolation directly: its Agent state is shaped as
%{sandbox_id => %{key => value}} and every operation (core cache, hash,
json, scan, plus all ETS/DETS-style helpers) reads and writes only the
sub-map for the current caller's sandbox_id. The sandbox_id is resolved
via SandboxRegistry in the caller process before entering the Agent
callback, so caller $callers/$ancestors resolution still works. Falls
back to :__unscoped__ when no registry entry exists.

With isolation handled inside the adapter, Cache no longer needs to
prefix keys in sandbox mode (maybe_sandbox_key is now a plain no-op) and
Cache.Redis no longer threads a sandbox_prefix into scan opts.
@MikaAK MikaAK merged commit 342df01 into main Apr 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant