v2.0.0-alpha.19
Pre-releaseVersion v2.0.0-alpha.19 (2026-04-18)
Hardening release that resolves a 15-Blocker audit covering JWT validation, CORS, rate limiting, ORM portability, RFC 7807 error semantics, CLI safety, and documentation/skill drift. 253 tests / 548 assertions pass on PHP 8.4 across SQLite, MySQL, and Postgres targets.
π‘οΈ Security
- JWT claim validation is now unconditional.
Auth::decode()rejects tokens missing any ofiss,aud,sub,jti, orexp. Previously, missing env vars silently disabled the corresponding claim check β tokens without identity claims could be accepted. - CORS wildcard + credentials is a fatal misconfiguration.
CorsMiddlewarethrowsInvalidArgumentExceptionat construction whenCORS_ALLOWED_ORIGINS=*is combined withCORS_ALLOW_CREDENTIALS=true(W3C spec forbids this combination). New env varCORS_ALLOW_CREDENTIALS(defaultfalse). - Rate-limit IP spoofing closed.
X-Forwarded-Foris honored only whenREMOTE_ADDRis inRATE_LIMIT_TRUSTED_PROXIES(comma-separated, default empty). Without a trusted-proxy list, the rawREMOTE_ADDRis used, so clients can no longer set their own rate-limit key. - SQL-injection via identifier parameters is eliminated.
?sort=and?fields=are filtered throughQueryBuilder::getColumnAllowlist()before reaching SQL.?sort=id;DROP TABLE users--is normalized toORDER BY id ASC. php api serveis shell-free.ServeCommandnow spawns PHP viaproc_openwith an argv array; host/port/router arguments are never shell-expanded.- Swagger UI / ReDoc HTML is XSS-safe.
DocsControllerescapes spec metadata before interpolating into the rendered HTML shell.
π New Features
-
ValidationException(Coagus\PhpApiBuilder\Exceptions\ValidationException) β final exception thrown byEntity::save(), exposespublic readonly array $errorskeyed by field name.ErrorHandlermaps it to a 422 RFC 7807 response with errors embedded.try { $product->save(); } catch (ValidationException $e) { return $this->error($e->getMessage(), 422, ['errors' => $e->errors]); }
-
EntityNotFoundExceptionβ canonical way to produce a 404.ErrorHandlerdispatches on class, not on message substring.$user = User::find($id); if ($user === null) { throw new EntityNotFoundException("User {$id} not found"); }
-
Driver session hooks β
DriverInterfacegainsgetCurrentTimestampExpression(),applySessionSettings(\PDO), andgetRefreshTokenTableDdl(). Applied automatically on connect:- SQLite:
PRAGMA foreign_keys=ON,PRAGMA journal_mode=WAL,PRAGMA busy_timeout=5000. - MySQL:
SET NAMES utf8mb4,SET time_zone='+00:00'. - Postgres:
SET TIME ZONE 'UTC',SET client_encoding='UTF8'.
- SQLite:
-
QueryBuilder::getColumnAllowlist(): list<string>β public API consumed byAPIDBto validate identifier inputs. Overridable per entity. -
docs:generate --namespace=Appβ override the root scanned namespace when emitting the OpenAPI spec.
π§ Improvements
- RFC 7807 everywhere for errors. All error responses emit
Content-Type: application/problem+json; charset=utf-8with{type, title, status, detail, requestId}. Success responses keepapplication/json. docs:generateregisters entities AND services. Services were previously skipped silently.env:checkreports the correct minimum PHP 8.4, matchingcomposer.json.- Portable soft-delete.
Entity::delete()soft-delete uses the driver'sgetCurrentTimestampExpression()instead of hardcodedNOW(), working across MySQL, Postgres, and SQLite. orWhere()with soft-delete is parenthesised. Thedeleted_at IS NULLpredicate can no longer be bypassed by adding anorWhereclause.- Arch test added (
tests/Arch/NoSqliteOnlySqlTest.php) guarding against SQLite-only SQL creeping back into driver-agnostic code.
π Bug Fixes
Entity::delete()soft-delete no longer fails on SQLite/Postgres (NOW()was MySQL-only).QueryBuilder::orWhere()emits well-formed SQL when used as the first clause in a chain.ErrorHandlerno longer leaks raw exception messages in production for unmapped\Throwables β returns a generic "Internal Server Error" while logging the original.- Stale
tests/Integration/APIDB/DeleteTest.phpupdated to account forPRAGMA foreign_keys=ONon SQLite.
π₯ Breaking Changes
Entity::save()throwsValidationException(not\RuntimeException). Catchers of\RuntimeExceptionfor validation must migrate.ErrorHandlerdispatches on exception class, not message content. ThrowEntityNotFoundExceptionfor 404; a\RuntimeException('... not found')now yields 500.CORS_ALLOWED_ORIGINS=*+CORS_ALLOW_CREDENTIALS=truenow throwsInvalidArgumentExceptionat boot.- Custom
DriverInterfaceimplementations must add the three new methods. - Error
Content-Typeis nowapplication/problem+json; charset=utf-8. Clients that switch onContent-Typemust accept both JSON variants.
π Technical Details
- 15 atomic commits (Conventional Commits), one concern per commit, identity
Christian Agustin <christian@agustin.gt>. - New unit tests under
tests/Unit/{CLI,Exceptions,Http,OpenAPI}; new arch test undertests/Arch/; updated integration tests for APIDB, middleware, ORM, and auth. - Library-shipped skill bumped to
v2.0.0with full Migration Guide (resources/skill/php-api-builder/CHANGELOG.md). - Agent specs and specialization skills added under
.claude/agents/and.claude/skills/driving thedevelop β validate β document β releasepipeline (Dockerized PHP runtime viapab-devcontainer).
π Migration Notes
- Replace
catch (\RuntimeException)aroundEntity::save()withcatch (ValidationException)and read$e->errors. - Throw
EntityNotFoundExceptionto produce 404s; remove reliance on"not found"message matching. - If you run
CORS_ALLOWED_ORIGINS=*with credentials enabled, pick one of:- Explicit origins +
CORS_ALLOW_CREDENTIALS=true, or - Wildcard origin +
CORS_ALLOW_CREDENTIALS=false.
- Explicit origins +
- Custom drivers: implement
getCurrentTimestampExpression(),applySessionSettings(\PDO),getRefreshTokenTableDdl(). - Client code switching on
Content-Typemust acceptapplication/problem+json. - Behind a load balancer: set
RATE_LIMIT_TRUSTED_PROXIES=10.0.0.1,10.0.0.2(comma-separated). - Scripts/CI referencing phantom commands (
php api test,php api skill:install) β remove them; usevendor/bin/pestdirectly.
π¦ Installation
composer require coagus/php-api-builder:v2.0.0-alpha.19
docker pull coagus/php-api-builder:v2.0.0-alpha.19Full Changelog: v2.0.0-alpha.18...v2.0.0-alpha.19