@@ -350,21 +350,37 @@ def add_basemap(
350350 elif isinstance (crs , CRS ):
351351 crs = crs .to_string ()
352352
353- # Get tile provider
353+ # Get tile provider using safe whitelist approach
354+ # Security: Never use eval() with user-supplied input
355+ BASEMAP_PROVIDERS = {
356+ 'OpenStreetMap' : lambda : ctx .providers .OpenStreetMap .Mapnik ,
357+ 'OpenStreetMap.Mapnik' : lambda : ctx .providers .OpenStreetMap .Mapnik ,
358+ 'OpenStreetMap.HOT' : lambda : ctx .providers .OpenStreetMap .HOT ,
359+ 'CartoDB' : lambda : ctx .providers .CartoDB .Positron ,
360+ 'CartoDB.Positron' : lambda : ctx .providers .CartoDB .Positron ,
361+ 'CartoDB.DarkMatter' : lambda : ctx .providers .CartoDB .DarkMatter ,
362+ 'CartoDB_dark' : lambda : ctx .providers .CartoDB .DarkMatter ,
363+ 'Stamen' : lambda : ctx .providers .Stamen .Terrain ,
364+ 'Stamen.Terrain' : lambda : ctx .providers .Stamen .Terrain ,
365+ 'Stamen.Toner' : lambda : ctx .providers .Stamen .Toner ,
366+ 'Stamen.TonerLite' : lambda : ctx .providers .Stamen .TonerLite ,
367+ 'Stamen.Watercolor' : lambda : ctx .providers .Stamen .Watercolor ,
368+ 'ESRI' : lambda : ctx .providers .Esri .WorldImagery ,
369+ 'Esri.WorldImagery' : lambda : ctx .providers .Esri .WorldImagery ,
370+ 'Esri.WorldStreetMap' : lambda : ctx .providers .Esri .WorldStreetMap ,
371+ 'Esri.WorldTopoMap' : lambda : ctx .providers .Esri .WorldTopoMap ,
372+ 'Esri.NatGeoWorldMap' : lambda : ctx .providers .Esri .NatGeoWorldMap ,
373+ }
374+
354375 if isinstance (source , str ):
355- if source == 'OpenStreetMap' :
356- provider = ctx .providers .OpenStreetMap .Mapnik
357- elif source == 'CartoDB' :
358- provider = ctx .providers .CartoDB .Positron
359- elif source == 'CartoDB_dark' :
360- provider = ctx .providers .CartoDB .DarkMatter
361- elif source == 'Stamen' :
362- provider = ctx .providers .Stamen .Terrain
363- elif source == 'ESRI' :
364- provider = ctx .providers .Esri .WorldImagery
376+ if source in BASEMAP_PROVIDERS :
377+ provider = BASEMAP_PROVIDERS [source ]()
365378 else :
366- # Try to get from ctx.providers
367- provider = eval (f"ctx.providers.{ source } " )
379+ available = ', ' .join (sorted (BASEMAP_PROVIDERS .keys ()))
380+ raise ValueError (
381+ f"Unknown basemap source: '{ source } '. "
382+ f"Available options: { available } "
383+ )
368384 else :
369385 provider = source
370386
0 commit comments