PHPackages                             waffle-commons/security - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. waffle-commons/security

ActiveLibrary[Security](/categories/security)

waffle-commons/security
=======================

Security component for Waffle framework.

0.1.0-beta4(2w ago)1351MITPHPPHP ^8.5CI passing

Since Dec 16Pushed 2w agoCompare

[ Source](https://github.com/waffle-commons/security)[ Packagist](https://packagist.org/packages/waffle-commons/security)[ RSS](/packages/waffle-commons-security/feed)WikiDiscussions main Synced today

READMEChangelog (9)Dependencies (59)Versions (26)Used By (1)

[![Discord](https://camo.githubusercontent.com/b30f41baece56d71f7f496f7e39fd33a2a096221c66c648b350dd4fe14276c2e/68747470733a2f2f696d672e736869656c64732e696f2f646973636f72642f3735353238383030313539323033333339313f6c6f676f3d646973636f7264)](https://discord.gg/eKgywnfXr2)[![PHP Version Require](https://camo.githubusercontent.com/c77bda048d6d1617ee1a65bd89060dabfa28c64f0cb08fc92097d1c919cf18bc/687474703a2f2f706f7365722e707567782e6f72672f776166666c652d636f6d6d6f6e732f73656375726974792f726571756972652f706870)](https://packagist.org/packages/waffle-commons/security)[![PHP CI](https://github.com/waffle-commons/security/actions/workflows/main.yml/badge.svg)](https://github.com/waffle-commons/security/actions/workflows/main.yml)[![codecov](https://camo.githubusercontent.com/3d47638e96d557465d81271b48f9a9c041b63559e4b31d6d50686b119700b60f/68747470733a2f2f636f6465636f762e696f2f67682f776166666c652d636f6d6d6f6e732f73656375726974792f67726170682f62616467652e7376673f746f6b656e3d64373461633632612d373837322d343033352d386238622d626363336166313939316530)](https://codecov.io/gh/waffle-commons/security)[![Latest Stable Version](https://camo.githubusercontent.com/f045e08e69e241eb0c7c4eabaad03dc6e856d8d3c3bf50bb8636a07ea4e43023/687474703a2f2f706f7365722e707567782e6f72672f776166666c652d636f6d6d6f6e732f73656375726974792f76)](https://packagist.org/packages/waffle-commons/security)[![Latest Unstable Version](https://camo.githubusercontent.com/23ca55567a71a2056f44928ba7c8719963d3e8b82f1211de9ef9a93c5077a2cb/687474703a2f2f706f7365722e707567782e6f72672f776166666c652d636f6d6d6f6e732f73656375726974792f762f756e737461626c65)](https://packagist.org/packages/waffle-commons/security)[![Total Downloads](https://camo.githubusercontent.com/340ac1357e6ecf59ca991e5ab8a6a624e91566b2d94772bdf2587d49074657c1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f776166666c652d636f6d6d6f6e732f73656375726974792e737667)](https://packagist.org/packages/waffle-commons/security)[![Packagist License](https://camo.githubusercontent.com/4d71e26194260280804ae8745c72866e70ec38c2e57478dc6f707d302a4fc63c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f776166666c652d636f6d6d6f6e732f7365637572697479)](https://github.com/waffle-commons/security/blob/main/LICENSE.md)

Waffle Security Component
=========================

[](#waffle-security-component)

> **Release:** `0.1.0-beta4` | [`CHANGELOG.md`](./CHANGELOG.md)

Hierarchical Attribute-Based Access Control (ABAC) for the Waffle Framework with a **fail-closed default** (SEC-02), a fully **stateless HMAC CSRF subsystem** bound to a per-browser anonymous SID (SEC-01 option C), and a container decorator (`SecureContainer`) that hardens service retrieval. Security is enforced by PSR-15 middleware sitting between routing and dispatch.

Beta-2 status
-------------

[](#beta-2-status)

No behavioural changes since Beta-1 — lockstep version bump only. The security architecture remains as described below.

🆕 Beta-1 foundations (still current)
------------------------------------

[](#-beta-1-foundations-still-current)

- **Fail-closed ABAC** — `SecureContainer::analyze()` rejects any action without a `#[Voter]` unless explicitly tagged `#[PublicAccess]`. Missing policy is denial, not silent allow.
- **Stateless HMAC CSRF** — `CsrfTokenManager` issues self-validating signed tokens; **no cache, no Redis, no PHP sessions**. The HMAC binds to `(id, sessionId)` so a token cannot be replayed across forms or across browsers.
- **`AnonymousSessionMiddleware`** — issues the `WAFFLE_SID` cookie (32 random bytes, base64url, 30-day Max-Age, HttpOnly, SameSite=Lax, Secure on HTTPS) that anchors CSRF binding. Stateless across requests (FrankenPHP-safe).

📦 Installation
--------------

[](#-installation)

```
composer require waffle-commons/security
```

🧱 Surface
---------

[](#-surface)

ClassRole`Waffle\Commons\Security\Security``SecurityInterface` implementation. Reads `waffle.security.level` from `ConfigInterface` at construction (defaults to `1`).`Waffle\Commons\Security\Abstract\AbstractSecurity`Shared base storing the configured security level and providing the `analyze()` walk.`Waffle\Commons\Security\Middleware\SecurityMiddleware`PSR-15 middleware that runs `SecureContainer::analyze($controller, $method)` — fail-closed ABAC.`Waffle\Commons\Security\Middleware\AnonymousSessionMiddleware`PSR-15 middleware that issues / reuses the `WAFFLE_SID` cookie and publishes the SID as the `_anon_sid` request attribute. **Required upstream of `CsrfMiddleware`.**`Waffle\Commons\Security\Middleware\CsrfMiddleware`PSR-15 middleware enforcing `#[RequiresCsrfToken]`. Validates the signed token against `(id, sessionId)`.`Waffle\Commons\Security\Csrf\CsrfTokenManager``final readonly` stateless HMAC-SHA256 token manager. Constructor takes a 32+ byte secret.`Waffle\Commons\Security\Container\SecureContainer`Decorator over `Waffle\Commons\Contracts\Container\ContainerInterface`. **Beta-1:** `analyze()` is now fail-closed — empty voter list ⇒ `SecurityException(403)` unless `#[PublicAccess]` is present.`Waffle\Commons\Security\Rule\Level1Rule` … `Level10Rule`The ten built-in security levels (1 = public … 10 = god-mode).🚦 The security ladder
---------------------

[](#-the-security-ladder)

Each `LevelNRule` lives in `src/Rule/`. Levels are integer-coded via `Waffle\Commons\Contracts\Constant\Constant::SECURITY_LEVEL1 … SECURITY_LEVEL10`. The kernel reads `waffle.security.level` from the application's `app.yaml` and constructs `Security` with that level.

```
# config/app.yaml
waffle:
  security:
    level: 5  # Authenticated user with elevated permissions
```

```
use Waffle\Commons\Security\Security;
use Waffle\Commons\Contracts\Config\ConfigInterface;

$security = new Security($config); // reads waffle.security.level
$security->analyze($controller);   // throws SecurityExceptionInterface if rules fail
```

The exact constructor, verbatim from `src/Security.php`:

```
final class Security extends AbstractSecurity
{
    public function __construct(ConfigInterface $cfg)
    {
        $this->level = $cfg->getInt(key: 'waffle.security.level', default: 1) ?? 1;
    }
}
```

🏷️ `#[Rule]` — declaring required levels
----------------------------------------

[](#️-rule--declaring-required-levels)

The attribute lives in the contracts package (`Waffle\Commons\Contracts\Security\Attribute\Rule`). Apply it to controller methods or classes:

```
use Waffle\Commons\Contracts\Security\Attribute\Rule;
use Waffle\Commons\Contracts\Constant\Constant;

final class AdminController
{
    #[Rule(level: Constant::SECURITY_LEVEL10)]
    public function dangerous(): Response { /* … */ }
}
```

If `Security::analyze()` is invoked against a controller method that requires a level higher than the kernel's configured level, a `SecurityExceptionInterface` is thrown and the `ErrorHandlerMiddleware` renders it as RFC 7807 `403`.

🚪 Fail-closed ABAC + `#[PublicAccess]` (Beta-1 / SEC-02)
--------------------------------------------------------

[](#-fail-closed-abac--publicaccess-beta-1--sec-02)

A controller action without any `#[Voter]` is now **denied with HTTP `403`** unless it explicitly carries `#[PublicAccess]`. Forgetting to attach a voter no longer silently grants access — missing policy is treated as denial.

```
use Waffle\Commons\Contracts\Security\Attribute\PublicAccess;
use Waffle\Commons\Routing\Attribute\Route;

final class HealthController
{
    #[Route(path: '/health', name: 'health')]
    #[PublicAccess]
    public function ping(): Response { /* … */ }
}
```

A method-level `#[Voter]` always wins over a class-level `#[PublicAccess]`, so mixed-policy controllers stay safe.

🛂 CSRF — stateless signed double-submit with per-browser binding (Beta-1 / SEC-01)
----------------------------------------------------------------------------------

[](#-csrf--stateless-signed-double-submit-with-per-browser-binding-beta-1--sec-01)

`CsrfMiddleware` enforces `#[RequiresCsrfToken]` using HMAC-signed self-validating tokens. **No cache, no Redis, no PHP sessions.** Wire format (binary, then base64url):

```
nonce (16 bytes) || expiresAt (8 bytes BE uint64) || HMAC-SHA256(nonce || expiresAt || id || sessionId, secret)

```

Two pieces of context are folded into the HMAC:

- the **logical id** (e.g. `form:login`) — prevents cross-form replay;
- the **anonymous session id** (the `WAFFLE_SID` cookie value, published as the `_anon_sid` request attribute by `AnonymousSessionMiddleware`) — prevents cross-browser replay.

Operational requirements:

1. Provide a 32+ byte signing secret. Production refuses to boot without one. Config key `waffle.security.csrf.secret`, with env fallback `WAFFLE_CSRF_SECRET`.
2. Wire `AnonymousSessionMiddleware` **before** `CsrfMiddleware` in the pipeline. The skeleton's `AppKernelFactory` does this for you.

```
# config/app.yaml
waffle:
  security:
    level: 5
    csrf:
      secret: '%env(WAFFLE_CSRF_SECRET)%'
```

```
$csrfTokenManager = new CsrfTokenManager(secret: $csrfSecret);
$container->set(CsrfTokenManagerInterface::class, $csrfTokenManager);

$stack
    ->add(new AnonymousSessionMiddleware())
    ->add(new CoreRoutingMiddleware($router))
    ->add(new CsrfMiddleware($csrfTokenManager))
    ->add(new SecurityMiddleware($secureContainer, $logger));
```

🛡️ `SecureContainer`
--------------------

[](#️-securecontainer)

`Waffle\Commons\Security\Container\SecureContainer` wraps any `ContainerInterface` and runs the security check before `get($id)` returns the service — preventing low-privilege code paths from pulling sensitive services out of the container.

`analyze($controller, $method)` is **fail-closed** as of Beta-1: an empty `#[Voter]` list throws `SecurityException(403)` unless the target carries `#[PublicAccess]`. Otherwise every voter must approve (consensus pattern) for the call to proceed.

🐘 PHP 8.5 features used
-----------------------

[](#-php-85-features-used)

- Typed constructors throughout (`Security` takes `ConfigInterface`, level resolution is `?int ?? 1`).
- Typed integer security levels declared as typed constants in `Constant::SECURITY_LEVEL*`.
- `#[Rule]` / `#[Voter]` / `#[RequiresCsrfToken]` / `#[PublicAccess]` attributes from the contracts package.
- `final readonly class CsrfToken` value object; `final readonly class CsrfTokenManager` (no instance state across requests).
- `#[\SensitiveParameter]` on the CSRF signing secret to suppress its value from stack traces and error reports.

🧭 Architectural boundary (`mago guard`)
---------------------------------------

[](#-architectural-boundary-mago-guard)

An active dependency **perimeter** is enforced on every CI run by `vendor/bin/mago guard` (bundled into `composer mago`; zero baselines). The rules live in [`mago.toml`](./mago.toml) under `[guard.perimeter]` — a forbidden `use` statement fails the build, not a reviewer.

Production code under `Waffle\Commons\Security` may depend **only** on:

- `Waffle\Commons\Security\**` — itself
- `Waffle\Commons\Contracts\**` — the shared contracts package (`#[PublicAccess]`, `#[Voter]`, `RequiresCsrfToken`, the CSRF constants, etc.)
- `Waffle\Commons\Utils\**` — the `ClassParser` reflection helper used by `SecureContainer`
- `Psr\**` — PSR interfaces (PSR-7 / PSR-15)
- `@global` + `Psl\**` — PHP core and the PHP Standard Library

Test code under `WaffleTests\Commons\Security` is unrestricted (`@all`). Structural rules are guarded too: interfaces must be named `*Interface`, `Exception\**` classes must end in `*Exception`, and any `Enum\**` namespace may hold only `enum` declarations.

Contract-first, component-agnostic by construction: components compose through `waffle-commons/contracts` (plus the explicitly-permitted `utils`), never ad-hoc through one another.

🧪 Testing
---------

[](#-testing)

```
docker exec -w /waffle-commons/security waffle-dev composer tests
```

📄 License
---------

[](#-license)

MIT — see [LICENSE.md](./LICENSE.md).

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance96

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~22 days

Recently: every ~5 days

Total

9

Last Release

20d ago

PHP version history (2 changes)0.1.0-alpha3PHP ^8.4

0.1.0-alpha4PHP ^8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/34a7557a3fb23aaf788ca3892b9b7efdf96e753264bafd0599153c9e8a921316?d=identicon)[LesliePetrimaux](/maintainers/LesliePetrimaux)

---

Top Contributors

[![supa-chayajin](https://avatars.githubusercontent.com/u/695448?v=4)](https://github.com/supa-chayajin "supa-chayajin (78 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPsalm

Type Coverage Yes

### Embed Badge

![Health badge](/badges/waffle-commons-security/health.svg)

```
[![Health](https://phpackages.com/badges/waffle-commons-security/health.svg)](https://phpackages.com/packages/waffle-commons-security)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.9k19.5M1.8k](/packages/cakephp-cakephp)[tempest/framework

The PHP framework that gets out of your way.

2.2k34.4k15](/packages/tempest-framework)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[drupal/core-recommended

Locked core dependencies; require this project INSTEAD OF drupal/core.

6942.5M421](/packages/drupal-core-recommended)[typo3/cms-core

TYPO3 CMS Core

3713.2M5.1k](/packages/typo3-cms-core)[ecotone/ecotone

Enterprise architecture layer for Laravel and Symfony — CQRS, Event Sourcing, Durable Workflows (Sagas, Orchestrators), Projections, and Outbox messaging via PHP attributes.

564576.7k53](/packages/ecotone-ecotone)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
