Bevy version and features
53ddd5e (2026/04/03)
Problem
The asset system can deny access to absolute paths, and relative paths containing .. and .. See UnapprovedPathMode
This works for AssetServer::load and similar - they end up calling AssetServer::load_with_meta_transform which does a AssetPath::is_unapproved check (source)
But there's a bunch of load functions that don't do any checks:
AssetServer::load_erased
AssetServer::load_untyped
AssetServer::load_untyped_async
AssetServer::load_unknown_type_with_meta_transform
LoadContext::read_asset_bytes
There's also problems with NestedLoader, which is used to load assets within loaders. First, it doesn't inherit the override flag from load_override and similar, so UnapprovedPathMode::Deny is broken - loads will be falsely rejected. Second, various paths either have no checks or incorrect checks:
- Deferred:
should_load_dependencies = true,
- Static typed.
- Forbid works, but Deny is broken as
NestedLoader<'_, '_, StaticTyped, Deferred>::load calls load_with_meta_transform with override_unapproved hard coded to true.
- Dynamic and unknown typed.
- Calls
AssetServer::load_erased_with_meta_transform/load_unknown_type_with_meta_transform, which should do the check (but currently doesn't).
should_load_dependencies = false.
- Calls
get_or_create_path_handle which doesn't do any checks.
- Immediate:
Solution?
I started working on some unit tests and fixes - branch here. But I've hit pause due to some issues.
First, while most of the problems can be solved with more plumbing and checks, I'm not so sure about load_override semantics and NestedLoader. Is it actually correct to inherit the parent override? Is this transitive to all dependencies and immediate loads? How does this get routed through the system?
Second, maybe the checks are in the wrong place? Currently they're in AssetServer. But what if the checks were in AssetSource instead? So AssetPlugin::unapproved_path_mode would go away - instead individual asset sources would have their own parameter.
This has some upsides:
- Somewhat centralizes the checks.
- They do get duplicated across sources, but it's harder to miss a check elsewhere since almost everything goes through the source eventually.
- One flaw is
get_or_create_path_handle, which doesn't go through the source.
- Makes the
AssetServer less dependent on Path and OS specific quirks.
- Different sources can have different settings.
- Not sure if this is actually a good thing, but worth exploring?
- Might also remove the need for
UnapprovedPathMode::Deny and the NestedLoader override inheritance.
- The user can map the same path as two different sources with different approvals.
- So instead of calling
load_override("path") they call load("my_unapproved_source://path").
The big downside is ergonomics. Instead of setting a single AssetPlugin::unapproved_path_mode, the user has to set it on each source.
So I'm not sure about solutions, but I can make a PR with failing unit tests if desired.
Bevy version and features
53ddd5e (2026/04/03)
Problem
The asset system can deny access to absolute paths, and relative paths containing
..and.. See UnapprovedPathModeThis works for
AssetServer::loadand similar - they end up callingAssetServer::load_with_meta_transformwhich does aAssetPath::is_unapprovedcheck (source)But there's a bunch of load functions that don't do any checks:
AssetServer::load_erasedAssetServer::load_untypedAssetServer::load_untyped_asyncAssetServer::load_unknown_type_with_meta_transformLoadContext::read_asset_bytesThere's also problems with
NestedLoader, which is used to load assets within loaders. First, it doesn't inherit the override flag fromload_overrideand similar, soUnapprovedPathMode::Denyis broken - loads will be falsely rejected. Second, various paths either have no checks or incorrect checks:should_load_dependencies = true,NestedLoader<'_, '_, StaticTyped, Deferred>::loadcallsload_with_meta_transformwithoverride_unapprovedhard coded totrue.AssetServer::load_erased_with_meta_transform/load_unknown_type_with_meta_transform, which should do the check (but currently doesn't).should_load_dependencies = false.get_or_create_path_handlewhich doesn't do any checks.Solution?
I started working on some unit tests and fixes - branch here. But I've hit pause due to some issues.
First, while most of the problems can be solved with more plumbing and checks, I'm not so sure about
load_overridesemantics andNestedLoader. Is it actually correct to inherit the parent override? Is this transitive to all dependencies and immediate loads? How does this get routed through the system?Second, maybe the checks are in the wrong place? Currently they're in
AssetServer. But what if the checks were inAssetSourceinstead? SoAssetPlugin::unapproved_path_modewould go away - instead individual asset sources would have their own parameter.This has some upsides:
get_or_create_path_handle, which doesn't go through the source.AssetServerless dependent onPathand OS specific quirks.AssetSourcebecomes the authority for what paths actually mean (Move away from std::Path in bevy_assets #19079).UnapprovedPathMode::Denyand theNestedLoaderoverride inheritance.load_override("path")they callload("my_unapproved_source://path").The big downside is ergonomics. Instead of setting a single
AssetPlugin::unapproved_path_mode, the user has to set it on each source.So I'm not sure about solutions, but I can make a PR with failing unit tests if desired.