Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Agents
rennf93/laravel-guard
claude-code
cursor
middleware
guard-core
composer
ci
mkdocs
request-translation
laravel

Guidance for AI agents (including Claude Code) working in this repository.

Install
php artisan boost:add-skill rennf93/laravel-guard

Save this content to: AGENTS.md

# AGENTS.md
Guidance for AI agents (including Claude Code) working in this repository.

## Project Overview

rennf93/laravel-guard (https://github.com/Guard-Core/laravel-guard) is a Laravel middleware adapter for guard-core-php. It maps `Illuminate\Http\Request` objects into the guard-core engine and translates the engine's block verdicts back to Laravel-native responses. It works with Laravel 10, 11, and 12.

- Composer package `rennf93/laravel-guard`, type `library`, license MIT. No `version` field in composer.json (the psr15-guard convention); versions come from git tags, of which there are none, so composer installs it as `dev-main`.
- This repository contains NO security logic. Detection, rate limiting, bans, and verdicts all live in guard-core-php.
- PHP `^8.2`. Autoload is PSR-4: `RenzoFranceschini\GuardCoreLaravel\` maps to `src/`.
- Shipped tags: none. There are no git tags and no releases.
- Docs site: MkDocs Material in `docs/` (strict build in CI, gh-deploy on push to `master` touching docs sources). Runnable demos in `examples/` are exercised by the live-smoke workflow. `master` is protected: never push to it, never merge into it, never create tags or releases.

## Ecosystem Position

- `rennf93/guard-core-php` (https://github.com/Guard-Core/guard-core-php) is the engine. It owns `SecurityConfig`, `GuardEngine`, the `GuardRequest`/`GuardResponse` contracts, `HeaderBag`, `RequestState`, `RedisHandler`, and `GuardRedisException`. Every check, verdict, and block response body originates there.
- This package is the Laravel adapter for that engine: `GuardMiddleware` runs an Illuminate request through `GuardEngine::execute()`, `LaravelGuardRequest` implements the core `GuardRequest` contract over `Illuminate\Http\Request`, and `ResponseTranslator` converts `GuardResponse` objects to `Illuminate\Http\Response`.
- Composer constraint: `rennf93/guard-core-php: ^0.1.0` (composer.json `require`). `composer.lock` pins `v0.1.0` (dist zipball fetched from the GitHub VCS repository).
- Repository configuration in composer.json, in order:
  1. Path repository `../guard-core-php`, marked `"canonical": false` (resolves when a sibling checkout of the core exists; non-canonical, so other sources win on conflict).
  2. VCS fallback `https://github.com/Guard-Core/guard-core-php.git` (how fresh checkouts and CI actually resolve the core).
- `minimum-stability: dev` with `prefer-stable: true`, required until the core has a Packagist distribution. The README documents the same setup for consumers of this package.
- `config.platform.php: 8.2.0` pins dependency RESOLUTION to the lowest supported PHP. Without it, composer running on PHP 8.4 resolves Laravel 12's transitive symfony packages (error-handler, var-dumper, translation, event-dispatcher, clock) to their 8.x lines, which require PHP >= 8.4.1 and make the lock uninstallable on the 8.2/8.3 matrix legs. The pin makes one lock installable across 8.2-8.4. Keep it.

## Boundary Rules

The adapter adapts; the engine decides.

- MUST NOT implement detection rules, penetration signatures, rate limiting, IP blacklisting or whitelisting, banning, security headers, or CORS. None of that exists in `src/` and none may be added. README: "No security headers or CORS are added by this adapter." (Laravel/Symfony response mechanics put a `Date` and a private `Cache-Control` on every response object; the engine's own headers are copied exactly.)
- MUST depend on guard-core-php for every verdict. `GuardMiddleware::handle()` calls `GuardEngine::execute()` and acts only on its return value (`null` means pass, a `GuardResponse` means block).
- MUST stay Laravel-specific and engine-only. `src/` imports only `Illuminate\Http\*`, `Symfony\Component\HttpFoundation\Response`, and `RenzoFranceschini\GuardCore\*` classes. Any other import family is a boundary violation.
- MUST stay fail-closed: if the engine throws, `handle()` returns the engine's fail-closed response (`500 Security check failed`, overridable through the engine's `customErrorResponses`), never the downstream handler.
- MUST keep the middleware stateless: it holds no mutable state. PHP shared-nothing applies (per request under classic FPM, per worker under Octane/FrankenPHP); distributed rate limits, bans, and cloud-range caches require Redis.
- MUST NOT widen the bounded body read: `LaravelGuardRequest::MAX_BODY_BYTES = 262144` (256 KiB) matches the engine's full-scan window. The framework materializes the full body in memory (php://input); the engine only ever sees the capped prefix. Payloads beyond the prefix, or signatures split across its boundary, are not detected; that tradeoff is deliberate and engine-coupled.
- MUST NOT add a publishable config file or service provider: configuration is constructor options only (the consumer builds a `SecurityConfig` and a `GuardEngine`). That keeps illuminate/support (service provider machinery) out of the dependency tree.

The real adapter surface (all of `src/`, three final classes in namespace `RenzoFranceschini\GuardCoreLaravel`):

- `GuardMiddleware`. Constructor takes `GuardEngine $engine`. The constructor calls `$engine->initialize()`; a `GuardRedisException` is swallowed only when `$engine->config()->redisFailOpen` is true, otherwise construction fails closed by rethrowing. `handle(Illuminate\Http\Request $request, Closure $next): Symfony\Component\HttpFoundation\Response` wraps the request in `LaravelGuardRequest`, runs the engine, translates block verdicts via `ResponseTranslator`, and returns `$next($request)` unchanged on pass. There is no PSR-7 in this adapter and no bridge is needed: like psr15-guard, it implements the core's own `GuardRequest` contract directly over the framework request object, calling the engine pipeline directly. That is why psr/http-message, psr/http-factory, and a Laravel-to-PSR-7 conversion are not dependencies.
- `LaravelGuardRequest implements RenzoFranceschini\GuardCore\Request\GuardRequest`. Adapts URL path from `getPathInfo()` (empty string becomes `/`), scheme from `getScheme()`, full URL as scheme://`getHttpHost()` plus path plus query when non-empty, and a pure `urlReplaceScheme()`; upper-cased method; client host from `$request->ip()` (null when missing or empty); headers into a `HeaderBag` with multi-value lines joined as `a, b`; query params passed through from the query bag; and the body as a cached, bounded prefix with `bodyWasTruncated()`. Each instance carries its own `RequestState`.
- `ResponseTranslator`. `translate(GuardResponse): Illuminate\Http\Response` copies status code, body (null becomes an empty string), and all headers onto the Laravel response. Exact translation, no additions; `prepare()` is never called, so no Content-Type/charset is synthesized.

Client address semantics (deliberate, documented in the README): `clientHost()` maps onto `$request->ip()`. Without Laravel's TrustProxies middleware that is the connecting `REMOTE_ADDR`, and the engine's own `trusted_proxies` / `X-Forwarded-For` resolution applies. With Laravel's TrustProxies configured, Laravel resolves the forwarded chain first and the engine sees the resolved client. Pick one side to do the resolving; configuring both can double-hop.

## Quick Start

This machine has NO `php` and NO `composer` binary. Run everything through Docker (`php:8.3-cli` for tests, `composer:2` for composer), or let CI execute.

Local install path (per composer.json):

1. Make guard-core-php resolvable: either check out the core at a sibling directory `../guard-core-php` (the path repository) or rely on the VCS fallback `https://github.com/Guard-Core/guard-core-php.git`.
2. `composer install` (the lock already pins guard-core-php v0.1.0).
3. `composer lint`, then `composer test`.

Docker path used during development (host bind-mounts the ZZZ workspace so the sibling path repository resolves):

```
docker run --rm -v <workspace>:/work -w /work/laravel-guard -e REDIS_HOST=0 php:8.3-cli php bin/test_laravel.php
```

The CI-verified path (copy this when describing a working environment; from `.github/workflows/ci.yml`):

1. Checkout this repo.
2. Checkout `rennf93/guard-core-php` at ref `guard-core-port-php` into `core-checkout`, then `mv core-checkout ../guard-core-php` so the path repository resolves.
3. Set up PHP from the matrix (8.2, 8.3, or 8.4) with the `mbstring` extension, coverage none.
4. `composer install --no-interaction --no-progress`, then `composer update rennf93/guard-core-php --no-interaction`.
5. Run the php -l sweep, then `REDIS_HOST=127.0.0.1 php bin/test_laravel.php` against a `redis:7-alpine` service on port 6379.

## Development Commands

Composer scripts (composer.json `scripts`; these are the only two):

| Command | What it runs |
| --- | --- |
| `composer test` | `php bin/test_laravel.php` |
| `composer lint` | `for f in $(find src bin -name '*.php'); do php -l "$f" > /dev/null || exit 1; done && echo LINT_OK` |
| `mkdocs build --strict` | Build the docs site (run with `docker run --rm -v "$PWD":/work -w /work python:3.12-slim sh -c "pip install -q mkdocs-material && mkdocs build --strict"`; the `site/` output is gitignored) |
| `docker compose -f examples/simple_app/docker-compose.yml up --build -d --wait` | Bring up the simple example app plus Redis; then run the curl assertions from `.github/workflows/live-smoke.yml` |
| `docker compose -f examples/advanced_app/docker-compose.yml up --build -d --wait` | Same for the advanced example (assertions in `examples/advanced_app/README.md`) |

Direct commands used by CI (verified in `.github/workflows/ci.yml`; the same install, lint, and test steps appear in `scheduled-lint.yml`):

- `composer install --no-interaction --no-progress`
- `composer update rennf93/guard-core-php --no-interaction` (CI deliberately refreshes the core dependency on every run)
- `for f in $(find src bin -name '*.php'); do php -l "$f" > /dev/null || exit 1; done && echo LINT_OK` (same sweep as `composer lint`)
- `php bin/test_laravel.php` with env `REDIS_HOST=127.0.0.1`
- `composer audit` (Composer audit job, PHP 8.3)

`bin/` contains exactly one script: `bin/test_laravel.php` (the whole test suite, plain PHP, no PHPUnit). There is no Makefile, no PHPUnit config, no PHPStan, no PHP-CS-Fixer, and no docker setup in this repo.

## Project Structure

```
.github/dependabot.yml                Weekly dependabot: github-actions + composer (grouped)
.github/labels.yml                    Label registry for sync-labels
.github/labeler.yml                   PR area-label rules for labeler
.github/workflows/ci.yml              CI: test matrix php 8.2/8.3/8.4 + redis service + composer audit
.github/workflows/release.yml         Release Gate: on v* tags, requires green ci.yml, static-analysis.yml and live-smoke.yml on the tagged commit
.github/workflows/scheduled-lint.yml  Weekly cron (Mon 04:00 UTC): php -l sweep + composer audit
.github/workflows/issue-link.yml      PR must close an open issue or carry no-issue
.github/workflows/summary.yml         AI issue summary on the needs-summary label
.github/workflows/sync-labels.yml     Applies .github/labels.yml on push/dispatch
.github/workflows/greetings.yml       First-issue / first-PR welcome messages
.github/workflows/labeler.yml         Area labels from .github/labeler.yml
.github/workflows/stale.yml           Daily stale sweep with reminders
.github/workflows/live-smoke.yml      Dockerized compose smoke over examples/simple_app
.github/workflows/docs.yml            mkdocs strict build + gh-deploy on master docs changes
.github/workflows/container-release.yml  Publishes examples/advanced_app image to ghcr.io
.github/workflows/upstream-drift.yml  Daily suite run against guard-core-php@master
bin/test_laravel.php                  Entire test suite, plain PHP runner with a T assertion harness
composer.json                         Package metadata, autoload, scripts, repositories, platform pin
composer.lock                         Locked deps; tracked; regenerate only deliberately
docs/index.md                         Docs home: what the adapter is, install, quick start
docs/usage.md                         Middleware contract, registration, verdicts, admin-gate equivalent
docs/configuration.md                 SecurityConfig surface pointers, Redis, body bound
examples/simple_app/                  Minimal guarded app (compose app + redis), live-smoke target
examples/advanced_app/                Production-shaped app (env config, admin ban manager routes)
mkdocs.yml                            MkDocs Material site definition
src/GuardMiddleware.php               Laravel middleware
src/LaravelGuardRequest.php           Illuminate Request to GuardRequest adapter
src/ResponseTranslator.php            GuardResponse to Illuminate Response translator
src/.agents/skills/laravel-guard/     Package skill (SKILL.md)
LICENSE                               MIT, (c) 2026 Renzo Franceschini
README.md                             Install, usage, lifecycle, behavior notes
AGENTS.md / CLAUDE.md                 Agent guide (byte-identical copies)
```

- `src/` is the PSR-4 package root for `RenzoFranceschini\GuardCoreLaravel\`. It is flat: one final class per file, class name equals file name, no subdirectories. A fourth class would be a real surface change: update the README behavior notes in the same PR.
- `vendor/` exists on disk but is gitignored. Never `git add` it. `composer.lock` is tracked; never modify it casually.

## Technology Stack

- PHP `^8.2` (CI matrix: 8.2, 8.3, 8.4; the audit and scheduled-lint jobs run on 8.3).
- Runtime deps (composer.json `require`, with composer.lock versions):
  - `rennf93/guard-core-php ^0.1.0` (locked v0.1.0)
  - `illuminate/http ^10.0|^11.0|^12.0` (locked v12.69.2; Laravel 10, 11, and 12 supported)
  - `symfony/http-foundation ^6.4|^7.0` (locked v7.4.19; a direct dependency of illuminate/http, required explicitly because the middleware return type and the translator import it)
- `illuminate/contracts` is deliberately NOT a dependency: nothing in `src/` imports it.
- `config.platform.php: 8.2.0` in composer.json (see Ecosystem Position for why).
- No require-dev packages: the test suite needs nothing beyond the runtime deps (no nyholm, no orchestra/testbench; Laravel requests are built with `Illuminate\Http\Request::create`).
- CI runs a `redis:7-alpine` service container on port 6379 with health checks for the Redis integration tests.
- Actions are pinned by commit SHA: `actions/checkout` v7.0.1, `shivammathur/setup-php` 2.37.2, `actions/ai-inference` v3, `crazy-max/ghaction-github-labeler` v6.0.0, `actions/first-interaction` v3.1.0, `actions/labeler` v7.0.0, `actions/stale` v11.0.0, `docker/login-action` v4.6.0, `docker/setup-compose-action` v2.4.0.
- Examples run on `php:8.3-cli-alpine` (PHP built-in webserver, non-root in the advanced app) with composer builds from `composer:2`; Redis is `redis:7-alpine`.
- Docs site: mkdocs-material, strict build, deployed to GitHub Pages by `docs.yml` on `master` pushes touching `docs/**`, `mkdocs.yml`, `README.md`, or `src/**`.
- The examples are demo code, not package surface: they live under `examples/`, carry their own composer.json (no committed lock file), and must never be autoloaded by the library.

## Testing Guidelines

- Run with `composer test` (or `php bin/test_laravel.php`). Exit code 0 means green, 1 means red. Output: `ok - <label>` or `FAIL - <label>` per assertion, `=== section ===` headers, and a final `Passed: N, Failed: N` plus `N/N GREEN` or `N/N RED`.
- Redis integration: the runner attempts a socket connection to `REDIS_HOST` (default 127.0.0.1) and `REDIS_PORT` (default 6379). Set `REDIS_HOST=0` to force integration off; if no Redis is reachable it prints a SKIP line and unit coverage stands. CI sets `REDIS_HOST=127.0.0.1` against the redis service. Integration uses a random `REDIS_PREFIX=guard_core_laravel:<hex>:` and cleans keys before and after.
- Coverage areas (section names in `bin/test_laravel.php`): Illuminate to GuardRequest translation (URL parts, upper-cased method, `$request->ip()` missing or empty, header joining and case-insensitive lookup, query passthrough, body caching, per-instance state); bounded body read at exactly 262144 bytes (boundary, one byte over, truncation flag); block verdicts through the middleware (403 Forbidden for a blacklisted IP with the on_block hook payload and a Laravel-native response, 301 https enforcement with a Location header, 429 Too many requests with Retry-After: 60, 400 Suspicious activity detected from query or POST body); pass-through (downstream called exactly once with the original Illuminate request and its response returned unchanged); oversize bodies (payload beyond the cap never reaches the engine); whitelist; exclusion paths (including the favicon.ico default); passive mode; fail-closed on engine malfunction (500 Security check failed, customErrorResponses override, check-exception fail-secure); and Redis fail-open versus fail-closed construction.
- Any new engine behavior that flows through the adapter needs assertions here before the PR lands.
- The host machine cannot run the suite directly (no php binary); docker (`php:8.2-cli` / `php:8.3-cli` / `php:8.4-cli`) and CI are the executors.

## Code Quality Standards

- `declare(strict_types=1);` at the top of every PHP file in `src/` and `bin/`.
- `final` classes, `private readonly` promoted constructor properties, 4-space indentation, one class per file named after the class.
- Imports in `src/` are limited to `Illuminate\Http\*`, `Symfony\Component\HttpFoundation\Response`, and `RenzoFranceschini\GuardCore\*`. A new import outside those families is a boundary violation (see Boundary Rules).
- PHP 8.4-only syntax is forbidden (for example `new Foo()->bar()` without parentheses); the supported floor is 8.2.
- The php -l sweep over `src` and `bin` must pass; `composer lint` prints `LINT_OK` on success.
- Conventional commits are the house style (see `git log` of the sibling repos): `feat:`, `fix:`, `fix(deps):`, `ci:`, `chore:`, `chore(composer):`, `test:`, `docs:`. Lowercase, imperative, no attribution trailers.
- Dependabot keeps github-actions and composer dependencies fresh weekly (composer updates are grouped into one PR).

## Best Practices

- `master` is protected. Work on a branch, push, open a PR (draft PRs are fine). Never push to `master`, never tag, never publish a release as part of agent work.
- Never `git add vendor/`, `.DS_Store`, or any stray file. Stage explicit paths only.
- Keep the adapter thin. If a change adds detection, verdict logic, or response shaping beyond translation, it belongs in guard-core-php, not here.
- Configuration is constructor options: consumers build the `SecurityConfig` and `GuardEngine` themselves (typically in a service provider binding). Do not add a publishable config file or a config-array-to-SecurityConfig mapper; that is a parallel config surface.
- Preserve fail-closed semantics: never let an engine exception fall through to the downstream handler.
- CI checks out the core at branch `guard-core-port-php` into `../guard-core-php` so the path repository resolves, and `composer update rennf93/guard-core-php` floats within the `^0.1.0` constraint. The composer.json constraint (`^0.1.0`, tagged v0.1.0) is the source of truth for the dependency.
- Update README.md behavior notes in the same PR whenever adapter behavior changes.

## Related Projects

- `rennf93/guard-core-php`: https://github.com/Guard-Core/guard-core-php. The engine this adapter delegates to. `SecurityConfig`, `GuardEngine`, `GuardRequest`/`GuardResponse`, `HeaderBag`, `RequestState`, `RedisHandler`, and `GuardRedisException` live there. Resolved via the `../guard-core-php` path repository (canonical: false) with the VCS fallback `https://github.com/Guard-Core/guard-core-php.git`; CI checks out its `guard-core-port-php` branch.
- `rennf93/psr15-guard`: https://github.com/Guard-Core/psr15-guard. The PSR-15 sibling adapter; the template this repository mirrors.
- `rennf93/laravel-guard`: this repository, the Laravel adapter layer of the guard-core ecosystem.

AGENTS.md

Guidance for AI agents (including Claude Code) working in this repository.

Project Overview

rennf93/laravel-guard (https://github.com/Guard-Core/laravel-guard) is a Laravel middleware adapter for guard-core-php. It maps Illuminate\Http\Request objects into the guard-core engine and translates the engine's block verdicts back to Laravel-native responses. It works with Laravel 10, 11, and 12.

  • Composer package rennf93/laravel-guard, type library, license MIT. No version field in composer.json (the psr15-guard convention); versions come from git tags, of which there are none, so composer installs it as dev-main.
  • This repository contains NO security logic. Detection, rate limiting, bans, and verdicts all live in guard-core-php.
  • PHP ^8.2. Autoload is PSR-4: RenzoFranceschini\GuardCoreLaravel\ maps to src/.
  • Shipped tags: none. There are no git tags and no releases.
  • Docs site: MkDocs Material in docs/ (strict build in CI, gh-deploy on push to master touching docs sources). Runnable demos in examples/ are exercised by the live-smoke workflow. master is protected: never push to it, never merge into it, never create tags or releases.

Ecosystem Position

  • rennf93/guard-core-php (https://github.com/Guard-Core/guard-core-php) is the engine. It owns SecurityConfig, GuardEngine, the GuardRequest/GuardResponse contracts, HeaderBag, RequestState, RedisHandler, and GuardRedisException. Every check, verdict, and block response body originates there.
  • This package is the Laravel adapter for that engine: GuardMiddleware runs an Illuminate request through GuardEngine::execute(), LaravelGuardRequest implements the core GuardRequest contract over Illuminate\Http\Request, and ResponseTranslator converts GuardResponse objects to Illuminate\Http\Response.
  • Composer constraint: rennf93/guard-core-php: ^0.1.0 (composer.json require). composer.lock pins v0.1.0 (dist zipball fetched from the GitHub VCS repository).
  • Repository configuration in composer.json, in order:
    1. Path repository ../guard-core-php, marked "canonical": false (resolves when a sibling checkout of the core exists; non-canonical, so other sources win on conflict).
    2. VCS fallback https://github.com/Guard-Core/guard-core-php.git (how fresh checkouts and CI actually resolve the core).
  • minimum-stability: dev with prefer-stable: true, required until the core has a Packagist distribution. The README documents the same setup for consumers of this package.
  • config.platform.php: 8.2.0 pins dependency RESOLUTION to the lowest supported PHP. Without it, composer running on PHP 8.4 resolves Laravel 12's transitive symfony packages (error-handler, var-dumper, translation, event-dispatcher, clock) to their 8.x lines, which require PHP >= 8.4.1 and make the lock uninstallable on the 8.2/8.3 matrix legs. The pin makes one lock installable across 8.2-8.4. Keep it.

Boundary Rules

The adapter adapts; the engine decides.

  • MUST NOT implement detection rules, penetration signatures, rate limiting, IP blacklisting or whitelisting, banning, security headers, or CORS. None of that exists in src/ and none may be added. README: "No security headers or CORS are added by this adapter." (Laravel/Symfony response mechanics put a Date and a private Cache-Control on every response object; the engine's own headers are copied exactly.)
  • MUST depend on guard-core-php for every verdict. GuardMiddleware::handle() calls GuardEngine::execute() and acts only on its return value (null means pass, a GuardResponse means block).
  • MUST stay Laravel-specific and engine-only. src/ imports only Illuminate\Http\*, Symfony\Component\HttpFoundation\Response, and RenzoFranceschini\GuardCore\* classes. Any other import family is a boundary violation.
  • MUST stay fail-closed: if the engine throws, handle() returns the engine's fail-closed response (500 Security check failed, overridable through the engine's customErrorResponses), never the downstream handler.
  • MUST keep the middleware stateless: it holds no mutable state. PHP shared-nothing applies (per request under classic FPM, per worker under Octane/FrankenPHP); distributed rate limits, bans, and cloud-range caches require Redis.
  • MUST NOT widen the bounded body read: LaravelGuardRequest::MAX_BODY_BYTES = 262144 (256 KiB) matches the engine's full-scan window. The framework materializes the full body in memory (php://input); the engine only ever sees the capped prefix. Payloads beyond the prefix, or signatures split across its boundary, are not detected; that tradeoff is deliberate and engine-coupled.
  • MUST NOT add a publishable config file or service provider: configuration is constructor options only (the consumer builds a SecurityConfig and a GuardEngine). That keeps illuminate/support (service provider machinery) out of the dependency tree.

The real adapter surface (all of src/, three final classes in namespace RenzoFranceschini\GuardCoreLaravel):

  • GuardMiddleware. Constructor takes GuardEngine $engine. The constructor calls $engine->initialize(); a GuardRedisException is swallowed only when $engine->config()->redisFailOpen is true, otherwise construction fails closed by rethrowing. handle(Illuminate\Http\Request $request, Closure $next): Symfony\Component\HttpFoundation\Response wraps the request in LaravelGuardRequest, runs the engine, translates block verdicts via ResponseTranslator, and returns $next($request) unchanged on pass. There is no PSR-7 in this adapter and no bridge is needed: like psr15-guard, it implements the core's own GuardRequest contract directly over the framework request object, calling the engine pipeline directly. That is why psr/http-message, psr/http-factory, and a Laravel-to-PSR-7 conversion are not dependencies.
  • LaravelGuardRequest implements RenzoFranceschini\GuardCore\Request\GuardRequest. Adapts URL path from getPathInfo() (empty string becomes /), scheme from getScheme(), full URL as scheme://getHttpHost() plus path plus query when non-empty, and a pure urlReplaceScheme(); upper-cased method; client host from $request->ip() (null when missing or empty); headers into a HeaderBag with multi-value lines joined as a, b; query params passed through from the query bag; and the body as a cached, bounded prefix with bodyWasTruncated(). Each instance carries its own RequestState.
  • ResponseTranslator. translate(GuardResponse): Illuminate\Http\Response copies status code, body (null becomes an empty string), and all headers onto the Laravel response. Exact translation, no additions; prepare() is never called, so no Content-Type/charset is synthesized.

Client address semantics (deliberate, documented in the README): clientHost() maps onto $request->ip(). Without Laravel's TrustProxies middleware that is the connecting REMOTE_ADDR, and the engine's own trusted_proxies / X-Forwarded-For resolution applies. With Laravel's TrustProxies configured, Laravel resolves the forwarded chain first and the engine sees the resolved client. Pick one side to do the resolving; configuring both can double-hop.

Quick Start

This machine has NO php and NO composer binary. Run everything through Docker (php:8.3-cli for tests, composer:2 for composer), or let CI execute.

Local install path (per composer.json):

  1. Make guard-core-php resolvable: either check out the core at a sibling directory ../guard-core-php (the path repository) or rely on the VCS fallback https://github.com/Guard-Core/guard-core-php.git.
  2. composer install (the lock already pins guard-core-php v0.1.0).
  3. composer lint, then composer test.

Docker path used during development (host bind-mounts the ZZZ workspace so the sibling path repository resolves):

docker run --rm -v <workspace>:/work -w /work/laravel-guard -e REDIS_HOST=0 php:8.3-cli php bin/test_laravel.php

The CI-verified path (copy this when describing a working environment; from .github/workflows/ci.yml):

  1. Checkout this repo.
  2. Checkout rennf93/guard-core-php at ref guard-core-port-php into core-checkout, then mv core-checkout ../guard-core-php so the path repository resolves.
  3. Set up PHP from the matrix (8.2, 8.3, or 8.4) with the mbstring extension, coverage none.
  4. composer install --no-interaction --no-progress, then composer update rennf93/guard-core-php --no-interaction.
  5. Run the php -l sweep, then REDIS_HOST=127.0.0.1 php bin/test_laravel.php against a redis:7-alpine service on port 6379.

Development Commands

Composer scripts (composer.json scripts; these are the only two):

Command What it runs
composer test php bin/test_laravel.php
composer lint `for f in $(find src bin -name '*.php'); do php -l "$f" > /dev/null
mkdocs build --strict Build the docs site (run with docker run --rm -v "$PWD":/work -w /work python:3.12-slim sh -c "pip install -q mkdocs-material && mkdocs build --strict"; the site/ output is gitignored)
docker compose -f examples/simple_app/docker-compose.yml up --build -d --wait Bring up the simple example app plus Redis; then run the curl assertions from .github/workflows/live-smoke.yml
docker compose -f examples/advanced_app/docker-compose.yml up --build -d --wait Same for the advanced example (assertions in examples/advanced_app/README.md)

Direct commands used by CI (verified in .github/workflows/ci.yml; the same install, lint, and test steps appear in scheduled-lint.yml):

  • composer install --no-interaction --no-progress
  • composer update rennf93/guard-core-php --no-interaction (CI deliberately refreshes the core dependency on every run)
  • for f in $(find src bin -name '*.php'); do php -l "$f" > /dev/null || exit 1; done && echo LINT_OK (same sweep as composer lint)
  • php bin/test_laravel.php with env REDIS_HOST=127.0.0.1
  • composer audit (Composer audit job, PHP 8.3)

bin/ contains exactly one script: bin/test_laravel.php (the whole test suite, plain PHP, no PHPUnit). There is no Makefile, no PHPUnit config, no PHPStan, no PHP-CS-Fixer, and no docker setup in this repo.

Project Structure

.github/dependabot.yml                Weekly dependabot: github-actions + composer (grouped)
.github/labels.yml                    Label registry for sync-labels
.github/labeler.yml                   PR area-label rules for labeler
.github/workflows/ci.yml              CI: test matrix php 8.2/8.3/8.4 + redis service + composer audit
.github/workflows/release.yml         Release Gate: on v* tags, requires green ci.yml, static-analysis.yml and live-smoke.yml on the tagged commit
.github/workflows/scheduled-lint.yml  Weekly cron (Mon 04:00 UTC): php -l sweep + composer audit
.github/workflows/issue-link.yml      PR must close an open issue or carry no-issue
.github/workflows/summary.yml         AI issue summary on the needs-summary label
.github/workflows/sync-labels.yml     Applies .github/labels.yml on push/dispatch
.github/workflows/greetings.yml       First-issue / first-PR welcome messages
.github/workflows/labeler.yml         Area labels from .github/labeler.yml
.github/workflows/stale.yml           Daily stale sweep with reminders
.github/workflows/live-smoke.yml      Dockerized compose smoke over examples/simple_app
.github/workflows/docs.yml            mkdocs strict build + gh-deploy on master docs changes
.github/workflows/container-release.yml  Publishes examples/advanced_app image to ghcr.io
.github/workflows/upstream-drift.yml  Daily suite run against guard-core-php@master
bin/test_laravel.php                  Entire test suite, plain PHP runner with a T assertion harness
composer.json                         Package metadata, autoload, scripts, repositories, platform pin
composer.lock                         Locked deps; tracked; regenerate only deliberately
docs/index.md                         Docs home: what the adapter is, install, quick start
docs/usage.md                         Middleware contract, registration, verdicts, admin-gate equivalent
docs/configuration.md                 SecurityConfig surface pointers, Redis, body bound
examples/simple_app/                  Minimal guarded app (compose app + redis), live-smoke target
examples/advanced_app/                Production-shaped app (env config, admin ban manager routes)
mkdocs.yml                            MkDocs Material site definition
src/GuardMiddleware.php               Laravel middleware
src/LaravelGuardRequest.php           Illuminate Request to GuardRequest adapter
src/ResponseTranslator.php            GuardResponse to Illuminate Response translator
src/.agents/skills/laravel-guard/     Package skill (SKILL.md)
LICENSE                               MIT, (c) 2026 Renzo Franceschini
README.md                             Install, usage, lifecycle, behavior notes
AGENTS.md / CLAUDE.md                 Agent guide (byte-identical copies)
  • src/ is the PSR-4 package root for RenzoFranceschini\GuardCoreLaravel\. It is flat: one final class per file, class name equals file name, no subdirectories. A fourth class would be a real surface change: update the README behavior notes in the same PR.
  • vendor/ exists on disk but is gitignored. Never git add it. composer.lock is tracked; never modify it casually.

Technology Stack

  • PHP ^8.2 (CI matrix: 8.2, 8.3, 8.4; the audit and scheduled-lint jobs run on 8.3).
  • Runtime deps (composer.json require, with composer.lock versions):
    • rennf93/guard-core-php ^0.1.0 (locked v0.1.0)
    • illuminate/http ^10.0|^11.0|^12.0 (locked v12.69.2; Laravel 10, 11, and 12 supported)
    • symfony/http-foundation ^6.4|^7.0 (locked v7.4.19; a direct dependency of illuminate/http, required explicitly because the middleware return type and the translator import it)
  • illuminate/contracts is deliberately NOT a dependency: nothing in src/ imports it.
  • config.platform.php: 8.2.0 in composer.json (see Ecosystem Position for why).
  • No require-dev packages: the test suite needs nothing beyond the runtime deps (no nyholm, no orchestra/testbench; Laravel requests are built with Illuminate\Http\Request::create).
  • CI runs a redis:7-alpine service container on port 6379 with health checks for the Redis integration tests.
  • Actions are pinned by commit SHA: actions/checkout v7.0.1, shivammathur/setup-php 2.37.2, actions/ai-inference v3, crazy-max/ghaction-github-labeler v6.0.0, actions/first-interaction v3.1.0, actions/labeler v7.0.0, actions/stale v11.0.0, docker/login-action v4.6.0, docker/setup-compose-action v2.4.0.
  • Examples run on php:8.3-cli-alpine (PHP built-in webserver, non-root in the advanced app) with composer builds from composer:2; Redis is redis:7-alpine.
  • Docs site: mkdocs-material, strict build, deployed to GitHub Pages by docs.yml on master pushes touching docs/**, mkdocs.yml, README.md, or src/**.
  • The examples are demo code, not package surface: they live under examples/, carry their own composer.json (no committed lock file), and must never be autoloaded by the library.

Testing Guidelines

  • Run with composer test (or php bin/test_laravel.php). Exit code 0 means green, 1 means red. Output: ok - <label> or FAIL - <label> per assertion, === section === headers, and a final Passed: N, Failed: N plus N/N GREEN or N/N RED.
  • Redis integration: the runner attempts a socket connection to REDIS_HOST (default 127.0.0.1) and REDIS_PORT (default 6379). Set REDIS_HOST=0 to force integration off; if no Redis is reachable it prints a SKIP line and unit coverage stands. CI sets REDIS_HOST=127.0.0.1 against the redis service. Integration uses a random REDIS_PREFIX=guard_core_laravel:<hex>: and cleans keys before and after.
  • Coverage areas (section names in bin/test_laravel.php): Illuminate to GuardRequest translation (URL parts, upper-cased method, $request->ip() missing or empty, header joining and case-insensitive lookup, query passthrough, body caching, per-instance state); bounded body read at exactly 262144 bytes (boundary, one byte over, truncation flag); block verdicts through the middleware (403 Forbidden for a blacklisted IP with the on_block hook payload and a Laravel-native response, 301 https enforcement with a Location header, 429 Too many requests with Retry-After: 60, 400 Suspicious activity detected from query or POST body); pass-through (downstream called exactly once with the original Illuminate request and its response returned unchanged); oversize bodies (payload beyond the cap never reaches the engine); whitelist; exclusion paths (including the favicon.ico default); passive mode; fail-closed on engine malfunction (500 Security check failed, customErrorResponses override, check-exception fail-secure); and Redis fail-open versus fail-closed construction.
  • Any new engine behavior that flows through the adapter needs assertions here before the PR lands.
  • The host machine cannot run the suite directly (no php binary); docker (php:8.2-cli / php:8.3-cli / php:8.4-cli) and CI are the executors.

Code Quality Standards

  • declare(strict_types=1); at the top of every PHP file in src/ and bin/.
  • final classes, private readonly promoted constructor properties, 4-space indentation, one class per file named after the class.
  • Imports in src/ are limited to Illuminate\Http\*, Symfony\Component\HttpFoundation\Response, and RenzoFranceschini\GuardCore\*. A new import outside those families is a boundary violation (see Boundary Rules).
  • PHP 8.4-only syntax is forbidden (for example new Foo()->bar() without parentheses); the supported floor is 8.2.
  • The php -l sweep over src and bin must pass; composer lint prints LINT_OK on success.
  • Conventional commits are the house style (see git log of the sibling repos): feat:, fix:, fix(deps):, ci:, chore:, chore(composer):, test:, docs:. Lowercase, imperative, no attribution trailers.
  • Dependabot keeps github-actions and composer dependencies fresh weekly (composer updates are grouped into one PR).

Best Practices

  • master is protected. Work on a branch, push, open a PR (draft PRs are fine). Never push to master, never tag, never publish a release as part of agent work.
  • Never git add vendor/, .DS_Store, or any stray file. Stage explicit paths only.
  • Keep the adapter thin. If a change adds detection, verdict logic, or response shaping beyond translation, it belongs in guard-core-php, not here.
  • Configuration is constructor options: consumers build the SecurityConfig and GuardEngine themselves (typically in a service provider binding). Do not add a publishable config file or a config-array-to-SecurityConfig mapper; that is a parallel config surface.
  • Preserve fail-closed semantics: never let an engine exception fall through to the downstream handler.
  • CI checks out the core at branch guard-core-port-php into ../guard-core-php so the path repository resolves, and composer update rennf93/guard-core-php floats within the ^0.1.0 constraint. The composer.json constraint (^0.1.0, tagged v0.1.0) is the source of truth for the dependency.
  • Update README.md behavior notes in the same PR whenever adapter behavior changes.

Related Projects

  • rennf93/guard-core-php: https://github.com/Guard-Core/guard-core-php. The engine this adapter delegates to. SecurityConfig, GuardEngine, GuardRequest/GuardResponse, HeaderBag, RequestState, RedisHandler, and GuardRedisException live there. Resolved via the ../guard-core-php path repository (canonical: false) with the VCS fallback https://github.com/Guard-Core/guard-core-php.git; CI checks out its guard-core-port-php branch.
  • rennf93/psr15-guard: https://github.com/Guard-Core/psr15-guard. The PSR-15 sibling adapter; the template this repository mirrors.
  • rennf93/laravel-guard: this repository, the Laravel adapter layer of the guard-core ecosystem.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource