Imported from nette/security (
AGENTS.md). Install upstream withnpx skills add nette/security. Copyright stays with the author.
To My Agents!
It is my fervent wish that this file guide every AI coding agent working with code in this repository.
Documentation
Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in docs/.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.
Almost all the expensive-to-reconstruct knowledge is in one subsystem - the
Permission ACL resolution engine - plus a few User and storage invariants.
This is security-critical: read docs/internals.md before touching
authorization logic.
Project Overview
Nette Security provides authentication (login/logout via pluggable
Authenticators), authorization (role-based ACL via Permission), and identity
persistence (UserStorage: session or cookie). Standalone library. Developers
implement the interfaces, never extend the concrete classes.
- PHP Version: 8.1 - 8.5
- Package:
nette/security
Essential Commands
# Run all tests
vendor/bin/tester tests -s # or: composer tester
vendor/bin/tester tests/Security.DI/ -s
# Static analysis (PHPStan level 8)
composer phpstan
Conventions
- Every file starts with
declare(strict_types=1);; tabs; single quotes unless the string has an apostrophe; noAbstract/Interface/Iprefixes; Nette Coding Standard. Mark password parameters with#[\SensitiveParameter]. - Tests are Nette Tester
.phpt(tests/Security/,tests/Security.DI/,tests/Security.Http/) plus PHPStan type tests intests/types/;Permission*.phptholds 40+ ACL scenarios. - Commit messages: lowercase imperative, no trailing period,
component: change(e.g.User: support for custom authenticators).
Working in this repo
Permissionrule types are plain booleans, not an enum:Allow = true,Deny = false,All = null(wildcard), andgetRuleType()returns?bool(null= no rule). The default state is deny-all (whitelist).isAllowed()resolves along three axes: walk the resource tree (a more specific resource wins over its parents) x DFS the role DAG (the most recently added parent has the highest weight) x privilege (a specific privilege beforeallPrivileges). For a whole-resource query (privilege = All), any single deny vetoes the blanket allow.- A failed
assertcallback makes the rule not apply (falls through as if absent) - except on the ultimate default rule, where it returns the inverted type. Assertions receive string IDs and reach objects viagetQueriedRole()/getQueriedResource();isAllowed()saves and restores the queried role/resource in afinally, so it is re-entrant (an assertion may callisAllowed()again) and exception-safe. - Effective roles come from login state, not the retained identity. After
logout()the identity is kept by default (persistIdentity), but roles drop to guest - which is whyisInRole()/isAllowed()need no priorisLoggedIn()check and a retained identity never leaks its privileges. loadStoredData()runs once;wakeupIdentity()may returnnullto revoke auth on a request (the per-request role-refresh/token seam);sleepIdentity()is itslogin()-time counterpart. Switching the storage namespace requiresrefreshStorage()orUserserves stale authentication.- User-facing how-to (fluent ACL API,
IdentityHandler/guest identity, password hashing, NEON config, presenter integration, multiple authenticators) is manual material and lives in the public web docs, not here.