Many / most access checkers (Route access checkers, hook_entity_access, hook_ENTITY_TYPE_access etc) get an $account passed in but this was not passed on to the AccessResult objects. In the rare case when this is not the current user, this resulted in incorrect caching. The solution is to simply pass on the AccountInterface $account:
Before:
return AccessResult::allowedIf($access)->cachePerPermissions();
After
return AccessResult::allowedIf($access)->cachePerPermissions($account);
Before
return AccessResult::neutral()->cachePerUser();
After:
return AccessResult::neutral()->cachePerUser($account);
In the rare case when there is no user to be had the new method cachePerCurrentUser can be called:
return AccessResult::neutral()->cachePerCurrentUser();
Note: to ensure backwards compatibility both cachePerUser and cachePerPermissions will continue to work in Drupal 8 without any arguments, however this is not recommended and it might even result in information disclosure in extremely rare cases: present some content to Ashley based on the permissions of Bailey and then revoke a role from Bailey, the content will still be visible for Ashley. Unless, of course, somewhere along the render tree $bailey_account was added as a cacheable dependency which is extremely likely.