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/guard-core-php
claude-code
cursor
conformance
telemetry
dynamic-rules
adapters
security-engine
redis-interop

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

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

Save this content to: AGENTS.md

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

## Project Overview

Guard-Core/guard-core-php (https://github.com/Guard-Core/guard-core-php) is the PHP port of guard-core: the framework-agnostic API security core engine for the guard-core ecosystem. It is a library, not an application: there is no server, no middleware, and nothing to deploy. Framework integration happens in the adapter repos (psr15-guard, slim-guard, laravel-guard, symfony-guard), each of which translates native requests into the engine's `GuardRequest` and block verdicts back into framework responses.

- Composer package `rennf93/guard-core-php`, type `library`, license MIT. PHP `^8.2` with `ext-pcre`, `ext-mbstring`, `ext-json` only (platform-only package: zero composer dependencies, `composer.lock` is a zero-dep lock).
- Autoload is PSR-4: `RenzoFranceschini\GuardCore\` maps to `src/`. Every file is `declare(strict_types=1)`.
- The semantic source of truth is the Python guard-core engine. The conformance fixture corpus (`conformance/guard-core-spec-4.1.0/`) carries expected values generated by the reference engine and must never be hand-written.

## Ecosystem Position

- `guard-core` (Python) is the reference engine; this repo ports it to PHP; guard-core-go ports it to Go. The adapters are psr15-guard (PSR-7/PSR-15), slim-guard (Slim 4 composition), laravel-guard (Illuminate), symfony-guard (HttpFoundation).
- guard-agent-php is the telemetry agent; the engine's telemetry seams are the event bus and metrics collector (an agent handler attached with `GuardEngine::setAgentHandler()`, which the adapters do) and the `onBlock` hook (`SecurityConfig(onBlock: ...)`, payload contract in `src/Pipeline/BlockEvents.php`). `enableAgent` is stored and declares that an agent handler is attached; `enableDynamicRules` and `enableEnrichment` require it (the reference `validate_agent_config`). The engine never builds the agent itself, so the reference `agent_api_key` field is not modelled.
- Dynamic rules: with `enableAgent` and `enableDynamicRules` on and an agent handler attached, `GuardEngine::initialize()` builds `Rules/DynamicRuleManager`, hydrates the last-known rules, and `execute()` fetches new rules every `dynamicRuleInterval` seconds (the reference background loop becomes a per-request interval gate because a PHP request process has no background task). A rule is applied exactly like the reference `_dynamic_rule_application.py`, over the base config (guard-core #169), never over the previous version.
- `customResponseModifier` (the reference `custom_response_modifier`) is applied once to every response the engine returns; adapters call `GuardEngine::applyResponseModifier()` for pass-through responses.
- Cross-implementation Redis interop lives in `bin/interop.php` (Python engine interop; requires `INTEROP_PHASE` and a reachable Redis).

## Architecture

All of `src/`, namespace `RenzoFranceschini\GuardCore`:

- `Engine/GuardEngine.php`: the facade. `__construct(SecurityConfig, ?RedisHandler, ?log, ?warn, ?CloudManager)` builds managers plus `CheckFactory` and the `SecurityCheckPipeline`; `initialize()` connects Redis and initializes ban/rate-limit/cloud state; `execute(GuardRequest): ?GuardResponse` resolves the client IP (fail-secure on unresolvable identity), honors `excludePaths`, and runs the pipeline. Accessors: `config()`, `redis()`, `banManager()`, `rateLimitHandler()`, `cloudManager()`, `responseFactory()`, `pipeline()`.
- `Config/SecurityConfig.php`: the single immutable config constructor (named arguments, every field nullable with an engine default) plus validation. `with()` returns an immutable copy with a bumped revision so the pipeline rebuilds. `Config/UnsupportedFeatureError.php`: the fail-closed error for features this port does not implement (none of the config options throws it today; `DeferredCheck` uses it for unknown check names).
- `Pipeline/`: `SecurityCheckPipeline` executes the 17 named checks in the fixed slot order of `CheckFactory::DEFAULT_CHECK_NAMES` (mirrors the Python `factory.py` slot order); `Checks/` holds one class per check; `BlockEvents.php` defines the `onBlock` payload and fires the hook (panics guarded, excluded check names per spec).
- `Detection/`: the detector (`SusPatterns` entry point), preprocessing, base64/truncation/semantic/xml-xxe/pickle/matchers, all pattern tables under `Support/Generated/` (generated by `tools/gen_tables.py`; never hand-edit) and the table-driven NFKC normalizer in `Support/Unicode.php` (fixtures generated by `tools/gen_nfkc_fixtures.py`).
- `Ban/IpBanManager.php`, `RateLimit/RateLimitHandler.php`, `Cloud/`: managers with Redis-backed state plus in-memory fallbacks. `Redis/`: a dependency-free RESP2 client (`RespConnection`, `RespPipeline`) and `RedisHandler` (reads `REDIS_HOST` / `REDIS_PORT` env, `REDIS_PREFIX` for keys).
- `Request/`: `GuardRequest` interface, `SimpleGuardRequest`, `HeaderBag`, `RequestState` (client IP, route id, bypass flags), `GuardResponse`/`GuardResponseFactory`, `ClientIpResolver` (trusted-proxy forwarded chain), `Ip/CanonicalIp` (canonicalization).
- `Routing/`: `RouteConfig` (per-route bypasses, rate limits, required headers, auth, time windows) and `RouteResolver` (resolves by `RequestState::$guardRouteId`).
- `Logging/`: request/suspicious logging with redaction (`LogRedactor`).

Known surface gaps (fail closed or documented, never silently no-op): no route registry on the `GuardEngine` facade (route configs resolve only when a caller wires them), fixed `Too many requests` 429 body (not tunable via `customErrorResponses`), in-process auto-ban violation counters.

## Boundary Rules

- MUST NOT add framework-specific code here: no PSR-7/PSR-15, no HTTP server, no web framework. The engine operates on its own `GuardRequest`/`GuardResponse` types.
- MUST NOT put security logic in adapters. All detection, banning, rate limiting, cloud blocking, and response decisions live here.
- MUST NOT change detection verdicts, scores, or shapes without regenerating the conformance fixtures from the reference engine (`tools/` scripts; expected values are generated, never hand-written).
- MUST NOT hand-edit `src/Support/Generated/` (pattern, HTML entity, and Unicode/NFKC tables) or `conformance/` expected values.
- MUST keep unsupported features failing closed (`UnsupportedFeatureError`). Do not silently no-op them.
- MUST NOT push to `master` (protected) and MUST NOT create tags; the release gate runs only on `v*` tags pushed by maintainers.
- MUST NOT commit `vendor/`, `site/`, `.DS_Store`, or `KNOWN_GAPS.md` (internal working notes, gitignored).
- No em-dashes or en-dashes anywhere in this repository.

## Quick Start

Run the runners with a host `php` and `composer` when present, else through Docker (`php:8.3-cli` for tests, `composer:2` for composer), or let CI execute. The host Redis on 6379 is reachable from containers as `host.docker.internal`.

```
docker run --rm -v "$PWD":/app -w /app composer:2 composer install --no-interaction --no-progress
docker run --rm -v "$PWD":/app -w /app -e REDIS_HOST=host.docker.internal php:8.3-cli php bin/test_state.php
docker run --rm -v "$PWD":/app -w /app php:8.3-cli php bin/conformance.php
```

## Development Commands

The `Makefile` wraps `install`, `test`, `lint`, `bump-version` and `clean`; every command below comes from the CI workflows, the composer scripts, or this file. Several runners (`test_ratelimit`, `test_geo_rate_limits`) issue `FLUSHDB` against the Redis they are pointed at, so give them a throwaway instance.

| Command | Purpose |
| --- | --- |
| `composer lint` | `php -l` sweep over `src` and `bin` (`for f in $(find src bin -name '*.php'); do php -l ...`) |
| `composer conformance` | `php bin/conformance.php`: replays the spec 4.1.0 fixture corpus |
| `php bin/test_state.php` | State/canonicalization suite (no Redis) |
| `REDIS_HOST=127.0.0.1 php bin/test_ratelimit.php` | Rate-limit handler suite |
| `REDIS_HOST=127.0.0.1 php bin/test_pipeline.php` | Pipeline and integration suite |
| `REDIS_HOST=127.0.0.1 php bin/test_m3b.php` / `test_m3c.php` / `test_m4.php` | Milestone suites (m3c is Redis-free) |
| `php bin/test_nfkc.php` | NFKC normalization conformance (no Redis) |
| `composer install` then `docker compose -f examples/simple_app/docker-compose.yml up --build -d --wait` | Live smoke stack for the simple example (advanced: `examples/advanced_app/docker-compose.yml`) |
| `pip install mkdocs-material && mkdocs build --strict` | Build the documentation site (CI deploys it on master) |

CI (`.github/workflows/ci.yml`) runs on every push and PR with a PHP matrix of 8.2/8.3/8.4 and a `redis:7-alpine` service: the `php -l` sweep, every `bin/` runner listed in the workflow (`test_*.php` and the `conformance*.php` replays, Redis-backed ones with `REDIS_HOST=127.0.0.1`), a pcov coverage job with a 100% line gate (`.github/coverage-runner.php`, waivers in `.github/coverage-unreachable.php`), plus a `composer audit` job (no-ops cleanly on the zero-dependency lock). Additional workflows: live smoke (both example apps, dockerized), ecosystem gate (the four adapter repos run their suites against this checkout), docs site (strict build + gh-deploy on master), issue link, labeler, stale, summary, greetings, sync labels, release gate at `v*` tags (requires the push runs of `ci.yml`, `live-smoke.yml` and `ecosystem-gate.yml` on the tagged commit to have succeeded; nothing is re-run), and scheduled weekly audit + lint.

## Testing Guidelines

- The suite is the `bin/test_*.php` runners plus the `bin/conformance*.php` replays, a T-harness style (`ok - / FAIL -` lines, exit code 0 only when everything passed). No PHPUnit, no test framework dependency.
- `REDIS_HOST` controls the integration legs: without it the runners exercise in-memory fallbacks; with it they assert real Redis state. Stale keys from prior runs can trip window-sensitive assertions, so run against a fresh database or flush between runs.
- The conformance corpus is drift control: any engine change that alters a verdict fails `bin/conformance.php`. Regenerate fixtures from the Python engine instead of updating expectations by hand.
- New behavior needs a test that would fail without it, added to the matching runner.

## Best Practices

1. Never push to `master` directly and never push `v*` tags; work on feature branches and PRs that say `Closes #N` (the issue-link check enforces it).
2. Conventional commits (`feat:`, `fix:`, `test:`, `docs:`, `ci:`), no AI attribution in commit messages or PR bodies.
3. Keep `declare(strict_types=1)` and PHP 8.2 compatibility (no 8.3+ syntax).
4. Keep the pipeline slot order and check names stable; they are part of the spec and mirrored in `SecurityConfig::CHECK_NAME_VALUES`.
5. Run the full `bin/` suite (with `REDIS_HOST`), the conformance runner, and `mkdocs build --strict` after touching docs before pushing.

## Related Projects

- guard-core (Python reference engine): https://github.com/Guard-Core/guard-core
- guard-core-go (Go port): https://github.com/Guard-Core/guard-core-go
- guard-agent-php (telemetry agent): https://github.com/Guard-Core/guard-agent-php
- psr15-guard / slim-guard / laravel-guard / symfony-guard (adapters): https://github.com/Guard-Core/psr15-guard and siblings
- guard-core-app (SaaS ingestion API): https://github.com/Guard-Core/guard-core-app

AGENTS.md

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

Project Overview

Guard-Core/guard-core-php (https://github.com/Guard-Core/guard-core-php) is the PHP port of guard-core: the framework-agnostic API security core engine for the guard-core ecosystem. It is a library, not an application: there is no server, no middleware, and nothing to deploy. Framework integration happens in the adapter repos (psr15-guard, slim-guard, laravel-guard, symfony-guard), each of which translates native requests into the engine's GuardRequest and block verdicts back into framework responses.

  • Composer package rennf93/guard-core-php, type library, license MIT. PHP ^8.2 with ext-pcre, ext-mbstring, ext-json only (platform-only package: zero composer dependencies, composer.lock is a zero-dep lock).
  • Autoload is PSR-4: RenzoFranceschini\GuardCore\ maps to src/. Every file is declare(strict_types=1).
  • The semantic source of truth is the Python guard-core engine. The conformance fixture corpus (conformance/guard-core-spec-4.1.0/) carries expected values generated by the reference engine and must never be hand-written.

Ecosystem Position

  • guard-core (Python) is the reference engine; this repo ports it to PHP; guard-core-go ports it to Go. The adapters are psr15-guard (PSR-7/PSR-15), slim-guard (Slim 4 composition), laravel-guard (Illuminate), symfony-guard (HttpFoundation).
  • guard-agent-php is the telemetry agent; the engine's telemetry seams are the event bus and metrics collector (an agent handler attached with GuardEngine::setAgentHandler(), which the adapters do) and the onBlock hook (SecurityConfig(onBlock: ...), payload contract in src/Pipeline/BlockEvents.php). enableAgent is stored and declares that an agent handler is attached; enableDynamicRules and enableEnrichment require it (the reference validate_agent_config). The engine never builds the agent itself, so the reference agent_api_key field is not modelled.
  • Dynamic rules: with enableAgent and enableDynamicRules on and an agent handler attached, GuardEngine::initialize() builds Rules/DynamicRuleManager, hydrates the last-known rules, and execute() fetches new rules every dynamicRuleInterval seconds (the reference background loop becomes a per-request interval gate because a PHP request process has no background task). A rule is applied exactly like the reference _dynamic_rule_application.py, over the base config (guard-core #169), never over the previous version.
  • customResponseModifier (the reference custom_response_modifier) is applied once to every response the engine returns; adapters call GuardEngine::applyResponseModifier() for pass-through responses.
  • Cross-implementation Redis interop lives in bin/interop.php (Python engine interop; requires INTEROP_PHASE and a reachable Redis).

Architecture

All of src/, namespace RenzoFranceschini\GuardCore:

  • Engine/GuardEngine.php: the facade. __construct(SecurityConfig, ?RedisHandler, ?log, ?warn, ?CloudManager) builds managers plus CheckFactory and the SecurityCheckPipeline; initialize() connects Redis and initializes ban/rate-limit/cloud state; execute(GuardRequest): ?GuardResponse resolves the client IP (fail-secure on unresolvable identity), honors excludePaths, and runs the pipeline. Accessors: config(), redis(), banManager(), rateLimitHandler(), cloudManager(), responseFactory(), pipeline().
  • Config/SecurityConfig.php: the single immutable config constructor (named arguments, every field nullable with an engine default) plus validation. with() returns an immutable copy with a bumped revision so the pipeline rebuilds. Config/UnsupportedFeatureError.php: the fail-closed error for features this port does not implement (none of the config options throws it today; DeferredCheck uses it for unknown check names).
  • Pipeline/: SecurityCheckPipeline executes the 17 named checks in the fixed slot order of CheckFactory::DEFAULT_CHECK_NAMES (mirrors the Python factory.py slot order); Checks/ holds one class per check; BlockEvents.php defines the onBlock payload and fires the hook (panics guarded, excluded check names per spec).
  • Detection/: the detector (SusPatterns entry point), preprocessing, base64/truncation/semantic/xml-xxe/pickle/matchers, all pattern tables under Support/Generated/ (generated by tools/gen_tables.py; never hand-edit) and the table-driven NFKC normalizer in Support/Unicode.php (fixtures generated by tools/gen_nfkc_fixtures.py).
  • Ban/IpBanManager.php, RateLimit/RateLimitHandler.php, Cloud/: managers with Redis-backed state plus in-memory fallbacks. Redis/: a dependency-free RESP2 client (RespConnection, RespPipeline) and RedisHandler (reads REDIS_HOST / REDIS_PORT env, REDIS_PREFIX for keys).
  • Request/: GuardRequest interface, SimpleGuardRequest, HeaderBag, RequestState (client IP, route id, bypass flags), GuardResponse/GuardResponseFactory, ClientIpResolver (trusted-proxy forwarded chain), Ip/CanonicalIp (canonicalization).
  • Routing/: RouteConfig (per-route bypasses, rate limits, required headers, auth, time windows) and RouteResolver (resolves by RequestState::$guardRouteId).
  • Logging/: request/suspicious logging with redaction (LogRedactor).

Known surface gaps (fail closed or documented, never silently no-op): no route registry on the GuardEngine facade (route configs resolve only when a caller wires them), fixed Too many requests 429 body (not tunable via customErrorResponses), in-process auto-ban violation counters.

Boundary Rules

  • MUST NOT add framework-specific code here: no PSR-7/PSR-15, no HTTP server, no web framework. The engine operates on its own GuardRequest/GuardResponse types.
  • MUST NOT put security logic in adapters. All detection, banning, rate limiting, cloud blocking, and response decisions live here.
  • MUST NOT change detection verdicts, scores, or shapes without regenerating the conformance fixtures from the reference engine (tools/ scripts; expected values are generated, never hand-written).
  • MUST NOT hand-edit src/Support/Generated/ (pattern, HTML entity, and Unicode/NFKC tables) or conformance/ expected values.
  • MUST keep unsupported features failing closed (UnsupportedFeatureError). Do not silently no-op them.
  • MUST NOT push to master (protected) and MUST NOT create tags; the release gate runs only on v* tags pushed by maintainers.
  • MUST NOT commit vendor/, site/, .DS_Store, or KNOWN_GAPS.md (internal working notes, gitignored).
  • No em-dashes or en-dashes anywhere in this repository.

Quick Start

Run the runners with a host php and composer when present, else through Docker (php:8.3-cli for tests, composer:2 for composer), or let CI execute. The host Redis on 6379 is reachable from containers as host.docker.internal.

docker run --rm -v "$PWD":/app -w /app composer:2 composer install --no-interaction --no-progress
docker run --rm -v "$PWD":/app -w /app -e REDIS_HOST=host.docker.internal php:8.3-cli php bin/test_state.php
docker run --rm -v "$PWD":/app -w /app php:8.3-cli php bin/conformance.php

Development Commands

The Makefile wraps install, test, lint, bump-version and clean; every command below comes from the CI workflows, the composer scripts, or this file. Several runners (test_ratelimit, test_geo_rate_limits) issue FLUSHDB against the Redis they are pointed at, so give them a throwaway instance.

Command Purpose
composer lint php -l sweep over src and bin (for f in $(find src bin -name '*.php'); do php -l ...)
composer conformance php bin/conformance.php: replays the spec 4.1.0 fixture corpus
php bin/test_state.php State/canonicalization suite (no Redis)
REDIS_HOST=127.0.0.1 php bin/test_ratelimit.php Rate-limit handler suite
REDIS_HOST=127.0.0.1 php bin/test_pipeline.php Pipeline and integration suite
REDIS_HOST=127.0.0.1 php bin/test_m3b.php / test_m3c.php / test_m4.php Milestone suites (m3c is Redis-free)
php bin/test_nfkc.php NFKC normalization conformance (no Redis)
composer install then docker compose -f examples/simple_app/docker-compose.yml up --build -d --wait Live smoke stack for the simple example (advanced: examples/advanced_app/docker-compose.yml)
pip install mkdocs-material && mkdocs build --strict Build the documentation site (CI deploys it on master)

CI (.github/workflows/ci.yml) runs on every push and PR with a PHP matrix of 8.2/8.3/8.4 and a redis:7-alpine service: the php -l sweep, every bin/ runner listed in the workflow (test_*.php and the conformance*.php replays, Redis-backed ones with REDIS_HOST=127.0.0.1), a pcov coverage job with a 100% line gate (.github/coverage-runner.php, waivers in .github/coverage-unreachable.php), plus a composer audit job (no-ops cleanly on the zero-dependency lock). Additional workflows: live smoke (both example apps, dockerized), ecosystem gate (the four adapter repos run their suites against this checkout), docs site (strict build + gh-deploy on master), issue link, labeler, stale, summary, greetings, sync labels, release gate at v* tags (requires the push runs of ci.yml, live-smoke.yml and ecosystem-gate.yml on the tagged commit to have succeeded; nothing is re-run), and scheduled weekly audit + lint.

Testing Guidelines

  • The suite is the bin/test_*.php runners plus the bin/conformance*.php replays, a T-harness style (ok - / FAIL - lines, exit code 0 only when everything passed). No PHPUnit, no test framework dependency.
  • REDIS_HOST controls the integration legs: without it the runners exercise in-memory fallbacks; with it they assert real Redis state. Stale keys from prior runs can trip window-sensitive assertions, so run against a fresh database or flush between runs.
  • The conformance corpus is drift control: any engine change that alters a verdict fails bin/conformance.php. Regenerate fixtures from the Python engine instead of updating expectations by hand.
  • New behavior needs a test that would fail without it, added to the matching runner.

Best Practices

  1. Never push to master directly and never push v* tags; work on feature branches and PRs that say Closes #N (the issue-link check enforces it).
  2. Conventional commits (feat:, fix:, test:, docs:, ci:), no AI attribution in commit messages or PR bodies.
  3. Keep declare(strict_types=1) and PHP 8.2 compatibility (no 8.3+ syntax).
  4. Keep the pipeline slot order and check names stable; they are part of the spec and mirrored in SecurityConfig::CHECK_NAME_VALUES.
  5. Run the full bin/ suite (with REDIS_HOST), the conformance runner, and mkdocs build --strict after touching docs before pushing.

Related Projects

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle