symfony-security-voters — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited symfony-security-voters (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You are an expert in Symfony security with Voters pattern within hexagonal architecture.
| Feature | Requires | Fallback |
|---|---|---|
#[IsGranted('ATTR', subject: 'argName')] | Symfony 6.2 | AuthorizationCheckerInterface::isGranted() |
?Vote $vote = null 4th voter parameter (reasons in profiler / logs) | Symfony 7.3 (part of the signature in 8.0) | Declare it anyway: harmless before 7.3 |
#[IsGranted] methods: option, #[CurrentUser] union types | Symfony 7.4 | separate routes / single user class |
#[IsGranted] with a static closure | Symfony 7.3 + PHP 8.5 | Voter |
subject: new Expression('...') | Symfony 6.2 | string argument name |
UserInterface::eraseCredentials() | deprecated 7.3 | do not keep plain passwords on the user |
No endpoint is publicly accessible without explicit `#[IsGranted]` or Voter check. Public endpoints use #[IsGranted('PUBLIC_ACCESS')] so the decision is visible.
#[Route('/api/users', methods: ['GET'])]
#[IsGranted('ROLE_USER_LIST')] // ALWAYS required
public function list(): JsonResponse { /* ... */ }Define roles in security.yaml:
security:
role_hierarchy:
ROLE_ADMIN: [ROLE_USER_CREATE, ROLE_USER_EDIT, ROLE_USER_DELETE, ROLE_USER_LIST, ROLE_USER_VIEW]
ROLE_MANAGER: [ROLE_USER_LIST, ROLE_USER_VIEW, ROLE_ORDER_MANAGE]
ROLE_USER: [ROLE_USER_VIEW]ROLE_{MODULE}_{ACTION}ROLE_USER_CREATE, ROLE_ORDER_VIEW, ROLE_REPORT_GENERATEROLE_ prefix: ORDER_EDIT#[IsGranted]#[IsGranted('ROLE_USER_CREATE')]
public function create(): JsonResponse { /* ... */ }When authorization depends on the resource ("can this user edit THIS order?"). Controllers only know the ID, so the voter receives the ID and loads what it needs through a read port: Presentation never touches Domain entities.
namespace App\Infrastructure\{Module}\Security;
use App\Domain\{Module}\Port\{Entity}ReadRepositoryInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Vote;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
/** @extends Voter<string, string> */
final class {Entity}Voter extends Voter
{
public const VIEW = '{MODULE}_VIEW';
public const EDIT = '{MODULE}_EDIT';
public function __construct(
private readonly {Entity}ReadRepositoryInterface $repository,
) {
}
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::VIEW, self::EDIT], true) && is_string($subject);
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
$view = $this->repository->findById($subject);
if ($view === null) {
$vote?->addReason('Resource not found');
return false;
}
return match ($attribute) {
self::VIEW, self::EDIT => $view->ownerId === $token->getUserIdentifier(),
default => false,
};
}
}#[Route('/{id}', methods: ['PUT'])]
#[IsGranted('{MODULE}_EDIT', subject: 'id')] // 'id' = controller argument name
public function update(string $id, #[MapRequestPayload] Update{Entity}Request $request): JsonResponse
{
$this->commandBus->dispatch(new Update{Entity}($id /* , ... */));
return $this->success(null);
}Use the attribute string ('ORDER_EDIT') in Presentation instead of importing the voter class from Infrastructure.
See references/ for detailed guides:
voter-patterns.md - Full voter examples and testingrole-hierarchy.md - Role hierarchy design, security.yaml, security user adapter~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.