@@ -227,31 +227,37 @@ def test_close(self):
227227 cache .close ()
228228
229229 def test_data_types (self ):
230- # Many different data types can be cached
230+ # Safe built-in types can be cached
231231 stuff = {
232232 'string' : 'this is a string' ,
233233 'int' : 42 ,
234234 'list' : [1 , 2 , 3 , 4 ],
235235 'tuple' : (1 , 2 , 3 , 4 ),
236236 'dict' : {'A' : 1 , 'B' : 2 },
237- 'function' : f ,
238- 'class' : C ,
239237 }
240238 cache .set ('stuff' , stuff )
241239 self .assertEqual (cache .get ('stuff' ), stuff )
242240
241+ def test_data_types_unsafe_rejected (self ):
242+ # Arbitrary functions and classes are blocked by SafeUnpickler (CVE-2025-69872)
243+ from diskcache .core import UnpicklingError
244+ cache .set ('fn' , f )
245+ with self .assertRaises (UnpicklingError ):
246+ cache .get ('fn' )
247+ cache .set ('cls' , C )
248+ with self .assertRaises (UnpicklingError ):
249+ cache .get ('cls' )
250+
243251 def test_cache_read_for_model_instance (self ):
244- # Don't want fields with callable as default to be called on cache read
252+ # Django model instances use django.db.models.base.model_unpickle which
253+ # is not in the SafeUnpickler allowlist (CVE-2025-69872).
254+ from diskcache .core import UnpicklingError
245255 expensive_calculation .num_runs = 0
246256 Poll .objects .all ().delete ()
247257 my_poll = Poll .objects .create (question = 'Well?' )
248- self .assertEqual (Poll .objects .count (), 1 )
249- pub_date = my_poll .pub_date
250258 cache .set ('question' , my_poll )
251- cached_poll = cache .get ('question' )
252- self .assertEqual (cached_poll .pub_date , pub_date )
253- # We only want the default expensive calculation run once
254- self .assertEqual (expensive_calculation .num_runs , 1 )
259+ with self .assertRaises (UnpicklingError ):
260+ cache .get ('question' )
255261
256262 def test_cache_write_for_model_instance_with_deferred (self ):
257263 # Don't want fields with callable as default to be called on cache write
@@ -267,21 +273,16 @@ def test_cache_write_for_model_instance_with_deferred(self):
267273 self .assertEqual (expensive_calculation .num_runs , 1 )
268274
269275 def test_cache_read_for_model_instance_with_deferred (self ):
270- # Don't want fields with callable as default to be called on cache read
276+ # Deferred querysets reference user model classes not in the SafeUnpickler
277+ # allowlist (CVE-2025-69872).
278+ from diskcache .core import UnpicklingError
271279 expensive_calculation .num_runs = 0
272280 Poll .objects .all ().delete ()
273281 Poll .objects .create (question = 'What?' )
274- self .assertEqual (expensive_calculation .num_runs , 1 )
275282 defer_qs = Poll .objects .all ().defer ('question' )
276- self .assertEqual (defer_qs .count (), 1 )
277283 cache .set ('deferred_queryset' , defer_qs )
278- self .assertEqual (expensive_calculation .num_runs , 1 )
279- runs_before_cache_read = expensive_calculation .num_runs
280- cache .get ('deferred_queryset' )
281- # We only want the default expensive calculation run on creation and set
282- self .assertEqual (
283- expensive_calculation .num_runs , runs_before_cache_read
284- )
284+ with self .assertRaises (UnpicklingError ):
285+ cache .get ('deferred_queryset' )
285286
286287 def test_expiration (self ):
287288 # Cache values can be set to expire
@@ -869,14 +870,12 @@ def test_custom_key_func(self):
869870 self .assertEqual (caches ['custom_key2' ].get ('answer2' ), 42 )
870871
871872 def test_cache_write_unpicklable_object (self ):
872- fetch_middleware = FetchFromCacheMiddleware (empty_response )
873+ # HttpResponse and SimpleCookie are not in the SafeUnpickler allowlist
874+ # (CVE-2025-69872). Writing succeeds but reading raises UnpicklingError.
875+ from diskcache .core import UnpicklingError
873876
874877 request = self .factory .get ('/cache/test' )
875878 request ._cache_update_cache = True
876- get_cache_data = FetchFromCacheMiddleware (
877- empty_response
878- ).process_request (request )
879- self .assertIsNone (get_cache_data )
880879
881880 content = 'Testing cookie serialization.'
882881
@@ -885,19 +884,11 @@ def get_response(req):
885884 response .set_cookie ('foo' , 'bar' )
886885 return response
887886
888- update_middleware = UpdateCacheMiddleware (get_response )
889- response = update_middleware (request )
887+ UpdateCacheMiddleware (get_response )(request )
890888
891- get_cache_data = fetch_middleware .process_request (request )
892- self .assertIsNotNone (get_cache_data )
893- self .assertEqual (get_cache_data .content , content .encode ())
894- self .assertEqual (get_cache_data .cookies , response .cookies )
895-
896- UpdateCacheMiddleware (lambda req : get_cache_data )(request )
897- get_cache_data = fetch_middleware .process_request (request )
898- self .assertIsNotNone (get_cache_data )
899- self .assertEqual (get_cache_data .content , content .encode ())
900- self .assertEqual (get_cache_data .cookies , response .cookies )
889+ fetch_middleware = FetchFromCacheMiddleware (empty_response )
890+ with self .assertRaises (UnpicklingError ):
891+ fetch_middleware .process_request (request )
901892
902893 def test_add_fail_on_pickleerror (self ):
903894 # Shouldn't fail silently if trying to cache an unpicklable type.
0 commit comments