The font library screen is gated on edit_theme_options, which on this site was granted to a role created in 2019 for something else.
// the audit
$ wp cap list editor | grep -c edit_theme_options
0
$ wp cap list content_manager | grep -c edit_theme_options
1 # a custom role, granted in 2019 for the
# customiser's menu screen
// the fix, scoped rather than blanket
add_filter( 'user_has_cap', function ( $caps, $req, $args, $user ) {
if ( in_array( 'install_fonts', $req, true )
&& ! user_can( $user, 'manage_options' ) ) {
$caps['install_fonts'] = false;
}
return $caps;
}, 10, 4 );
A capability granted for one screen in 2019 becomes a grant for every screen added afterwards that uses the same capability, which is the general hazard with a coarse permission model. Auditing custom roles after a WordPress release that adds a screen is a five-minute job that nobody has on a checklist, and this is the argument for putting it on one.