It’s in LXD itself, we have uid/gid shifting logic that kicks in either at creation or first start of the container. That’s unless you’re using shiftfs which then has the kernel doing the shifting in-kernel rather than on-disk (but based on your description, you’re not).
LXD’s shifting logic is aware of capabilities and acl extended attributes, we read those, translate them to their namespace-specific variant and write them.