Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 10 additions & 116 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
# ==============================================================================
# FILAMENT ACTIONGUARD - ENTERPRISE ENVIRONMENT CONFIGURATION
# ==============================================================================
# Local-development example only. Do not copy it to production unchanged.
# ==============================================================================
# Filament ActionGuard local Workbench configuration.
# Local development only; review every value before using it elsewhere.

# ------------------------------------------------------------------------------
# 1. APPLICATION & GENERAL SECURITY
# ------------------------------------------------------------------------------
APP_NAME="Filament ActionGuard"
APP_ENV=local
APP_KEY=
Expand All @@ -15,133 +9,33 @@ APP_URL=http://localhost:8000
APP_TIMEZONE=UTC
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=cache

# ------------------------------------------------------------------------------
# 2. LOGGING & AUDIT TRAIL
# ------------------------------------------------------------------------------
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
LOG_DAILY_DAYS=14

# ------------------------------------------------------------------------------
# 3. DATABASE CONFIGURATION (Multi-Engine Resilience)
# ------------------------------------------------------------------------------
# Default: SQLite for zero-latency local development & testbench
DB_CONNECTION=sqlite
DB_DATABASE="${APP_BASE_PATH:-.}/workbench/database.sqlite"
DB_FOREIGN_KEYS=true

# Enterprise PostgreSQL Profile (Supports pgvector for Semantic Search)
# DB_CONNECTION=pgsql
# DB_HOST=127.0.0.1
# DB_PORT=5432
# DB_DATABASE=actionguard_enterprise
# DB_USERNAME=actionguard_app
# DB_PASSWORD=your_secure_password
# DB_SSLMODE=prefer

# Enterprise MySQL/MariaDB Profile (Strict Mode Compliant)
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=actionguard_enterprise
# DB_USERNAME=actionguard_app
# DB_PASSWORD=your_secure_password

# ------------------------------------------------------------------------------
# 4. SESSION & COOKIE SETTINGS
# ------------------------------------------------------------------------------
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=true
# Laravel 13 default: json serialization prevents PHP deserialization gadget chain attacks
SESSION_SERIALIZATION=json
SESSION_PATH=/
SESSION_DOMAIN=null
# Set true in every HTTPS deployment.
SESSION_SECURE_COOKIE=false
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax

# ------------------------------------------------------------------------------
# 5. CACHING & APPLICATION ACCELERATION (Laravel 13 Standards)
# ------------------------------------------------------------------------------
CACHE_STORE=file
# Laravel 13 uses hyphenated prefix standard: {app-name}-cache-
CACHE_PREFIX="actionguard-cache-"
# Laravel 13 security: restricts arbitrary cached object unserialization
CACHE_SERIALIZABLE_CLASSES=false

# ------------------------------------------------------------------------------
# 6. QUEUE & ASYNC TASK PROCESSING (Laravel 13 Queue Routing)
# ------------------------------------------------------------------------------
QUEUE_CONNECTION=sync
QUEUE_FAILED_DRIVER=database-uuids

# ------------------------------------------------------------------------------
# 7. REDIS CONFIGURATION (Clustering & TLS Support)
# ------------------------------------------------------------------------------
REDIS_CLIENT=phpredis
REDIS_SCHEME=tcp
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_PREFIX="actionguard-database-"

# ------------------------------------------------------------------------------
# 8. FILAMENT 5 PANEL CONFIGURATION
# ------------------------------------------------------------------------------
FILAMENT_FILESYSTEM_DISK=public
FILAMENT_SERVE_ASSETS_FROM_VITE=false
FILAMENT_BROADCASTING_ENABLED=false

# ------------------------------------------------------------------------------
# 9. MAIL & NOTIFICATION DELIVERY
# ------------------------------------------------------------------------------
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="no-reply@actionguard.example.com"
MAIL_FROM_NAME="${APP_NAME}"

# ------------------------------------------------------------------------------
# 10. OBJECT STORAGE (AWS S3 / S3-Compatible Storage)
# ------------------------------------------------------------------------------
FILESYSTEM_DISK=local
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=eu-central-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

# ------------------------------------------------------------------------------
# 11. ACTIONGUARD POLICY SETTINGS
# ------------------------------------------------------------------------------
# Enable or disable ActionGuard evaluation globally. Keep true in production.
# Keep ActionGuard and fail-closed behavior enabled in production.
ACTIONGUARD_ENABLED=true

# Security posture: Fail-closed (true) blocks actions if a check throws an unhandled exception
ACTIONGUARD_FAIL_CLOSED=true

# Disabled by default. Enable only for a controlled, logged maintenance task.
# Enable only for a controlled and documented maintenance callback.
ACTIONGUARD_ALLOW_BYPASS=false

# Dispatch user-facing Filament danger notifications on state invariant violations
ACTIONGUARD_NOTIFICATIONS_ENABLED=true

# Enable data-minimised structured audit events; route the channel to your SIEM.
ACTIONGUARD_AUDIT_TRAIL=true
# Audit output is opt-in and data-minimised. Configure a suitable channel when enabled.
# Enable model type or state only for a documented and necessary audit purpose.
ACTIONGUARD_AUDIT_TRAIL=false
ACTIONGUARD_AUDIT_CHANNEL=stack
ACTIONGUARD_AUDIT_INCLUDE_MODEL_TYPE=false
ACTIONGUARD_AUDIT_INCLUDE_STATE=false

# Relative and HTTPS resolution links are allowed. HTTP is blocked by default.
# Relative paths and HTTPS are allowed. HTTP requires this explicit opt-in.
ACTIONGUARD_ALLOW_INSECURE_RESOLUTION_URLS=false
44 changes: 42 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ All notable changes to `filament-actionguard` will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.3.0] - 2026-09-14

### Added

- Publishable runtime configuration for enablement, fail-closed behavior,
notifications, guarded bypasses, audit logging, and resolution URL policy.
- Data-minimised structured audit events for action evaluation, invariant
blocking, and explicitly enabled bypass usage.
- Independent, disabled-by-default audit opt-ins for model types and state values;
unknown context fields are removed by a central event allow-list.
- Locked and prefer-lowest dependency verification in CI.
- Privacy responsibility and AI-generated artwork disclosures.
- Central resolution URL sanitization shared by Community and Enterprise checks.
- Filament Livewire integration coverage for table actions, page header actions,
modal halting, model validation errors, and notification configuration.

### Changed

- Composer now resolves stable dependencies by default and locks against the
minimum supported PHP 8.3 platform.
- ActionGuard bypasses are disabled by default and scoped to their callback.
- Resolution links allow HTTPS and relative URLs by default; HTTP requires an
explicit compatibility opt-in.
- Security policy now documents supported versions and response expectations.
- The local environment example now contains only Workbench and ActionGuard
settings and keeps audit logging disabled by default.

### Security

- Check exceptions are reported server-side with correlation IDs instead of
exposing internal exception messages in the Filament interface.
- Explicit checks are validated against `ActionGuardCheckContract`, and rich
action labels are stripped of HTML before use as modal headings.
- Direct `CheckResolution` URLs now use the same scheme allow-list and malformed
URL rejection as Enterprise resolutions.

## [1.0.0] - 2026-09-11

### Added
Expand All @@ -20,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `RequiredFieldCheck`: Validates presence and non-null status of critical model attributes.
- `NotEmptyCheck`: Validates strings, arrays, and collections with precise numeric zero (`0`, `'0'`, `0.00`) preservation.
- `ConditionCheck`: Custom boolean and closure-based rule evaluations with contextual failure messages.
- `RelationshipCheck`: Ensures required relationships exist and meet minimum count constraints.
- `RelationshipCheck`: Ensures required relationships resolve to a model or non-empty collection.
- `MediaCheck`: Verifies attachments via Spatie MediaLibrary collections or standard URL/path attributes.
- `CallbackCheck`: Flexible closure-driven check returning custom `CheckResult` instances with explicit severity and descriptions.

Expand Down Expand Up @@ -55,6 +93,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **Developer Experience & Tooling**
- Root `artisan` CLI bridge to Orchestra Testbench and Workbench environment.
- Comprehensive test suite with 103 Pest tests and 234 assertions covering architecture, unit checks, traits, enterprise bridge hardening, and demo product scenarios.
- Comprehensive test suite with 133 Pest tests and 315 assertions covering architecture, unit checks, traits, enterprise bridge hardening, Livewire integration, security, and demo product scenarios.

[1.0.0]: https://github.com/allgorithm/filament-actionguard/releases/tag/v1.0.0
[1.3.0]: https://github.com/allgorithm/filament-actionguard/releases/tag/v1.3.0
[Unreleased]: https://github.com/allgorithm/filament-actionguard/compare/v1.3.0...HEAD
52 changes: 45 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<a href="https://packagist.org/packages/allgorithm/filament-actionguard"><img src="https://img.shields.io/packagist/v/allgorithm/filament-actionguard.svg?style=flat-square&color=0ea5e9" alt="Latest Version on Packagist"></a>
<a href="https://php.net"><img src="https://img.shields.io/badge/PHP-8.3%20--%208.5-777BB4.svg?style=flat-square&logo=php&logoColor=white" alt="PHP 8.3 - 8.5"></a>
<a href="https://filamentphp.com"><img src="https://img.shields.io/badge/Filament-v5.x-FDAE4B.svg?style=flat-square&logo=laravel&logoColor=white" alt="Filament v5"></a>
<a href="https://pestphp.com"><img src="https://img.shields.io/badge/Pest-103%20Tests%20Passing-10b981.svg?style=flat-square&logo=pest" alt="Pest Tests"></a>
<a href="https://pestphp.com"><img src="https://img.shields.io/badge/Pest-133%20Tests%20Passing-10b981.svg?style=flat-square&logo=pest" alt="Pest Tests"></a>
<a href="https://phpstan.org"><img src="https://img.shields.io/badge/PHPStan-Level%208%20(0%20errors)-6366f1.svg?style=flat-square" alt="PHPStan Level 8"></a>
<a href="LICENSE.md"><img src="https://img.shields.io/badge/License-MIT-gray.svg?style=flat-square" alt="License MIT"></a>
</p>
Expand Down Expand Up @@ -62,7 +62,13 @@ ActionGuard introduces a robust **Two-Phase Invariant Defense System**:
- `MediaCheck`: Checks media collections (Spatie MediaLibrary or file upload paths).
- `CallbackCheck`: Flexible check with full `CheckResult` control.
- 🌍 **Fully Localized (i18n):** Complete English and German translations included out-of-the-box.
- 💎 **Bulletproof Quality:** PHPStan **Level 8** (0 errors), 100% PSR-12 code style, and 103 comprehensive Pest tests.
- 💎 **Bulletproof Quality:** PHPStan **Level 8** (0 errors), 100% PSR-12 code style, and 133 comprehensive Pest tests.

## Compatibility

| ActionGuard | PHP | Laravel | Filament |
| :--- | :--- | :--- | :--- |
| `1.x` | `8.3–8.5` | `11.28–13.x` (through Filament) | `5.x` |

---

Expand Down Expand Up @@ -207,15 +213,23 @@ ActionGuardAction::make('publish')
| `RequiredFieldCheck` | Attribute is not `null` | `RequiredFieldCheck::make('title')` |
| `NotEmptyCheck` | Value is not empty string/whitespace/empty array/empty collection (safely preserves `0`, `'0'`, `0.00`) | `NotEmptyCheck::make('price')` |
| `ConditionCheck` | Boolean callback closure | `ConditionCheck::make('min_stock', fn ($record) => $record->stock > 0, 'Stock must be greater than zero')` |
| `RelationshipCheck` | Eloquent relationship exists and is loaded | `RelationshipCheck::make('category')` |
| `RelationshipCheck` | Eloquent relationship resolves to a model or non-empty collection; it may lazy-load the relation | `RelationshipCheck::make('category')` |
| `MediaCheck` | Spatie MediaLibrary or array of image URLs | `MediaCheck::make('image_url')` |
| `CallbackCheck` | Evaluates custom logic returning `CheckResult` | `CallbackCheck::make('vat_id', fn ($record) => ...)` |

---

## ⚙️ Fluent Customization & Zero-Config
## ⚙️ Secure defaults & optional configuration

ActionGuard works with secure defaults without publishing a configuration file.
To review or override its operational safeguards, publish the package config:

```bash
php artisan vendor:publish --tag="filament-actionguard-config"
```

ActionGuard is designed as **Zero-Configuration by Default**. It requires no separate configuration file. You can customize the modal appearance, heading, and width directly on the action using Filament's fluent API:
You can customize the modal appearance, heading, and width directly on the
action using Filament's fluent API:

```php
ActionGuardAction::make('publish')
Expand All @@ -224,14 +238,36 @@ ActionGuardAction::make('publish')
->checks([...]);
```

The operational defaults and environment variables are:

| Setting | Environment variable | Default |
| :--- | :--- | :---: |
| `enabled` | `ACTIONGUARD_ENABLED` | `true` |
| `fail_closed` | `ACTIONGUARD_FAIL_CLOSED` | `true` |
| `allow_bypass` | `ACTIONGUARD_ALLOW_BYPASS` | `false` |
| `notifications` | `ACTIONGUARD_NOTIFICATIONS_ENABLED` | `true` |
| `audit.enabled` | `ACTIONGUARD_AUDIT_TRAIL` | `false` |
| `audit.channel` | `ACTIONGUARD_AUDIT_CHANNEL` | `null` |
| `audit.include_model_type` | `ACTIONGUARD_AUDIT_INCLUDE_MODEL_TYPE` | `false` |
| `audit.include_state` | `ACTIONGUARD_AUDIT_INCLUDE_STATE` | `false` |
| `allow_insecure_resolution_urls` | `ACTIONGUARD_ALLOW_INSECURE_RESOLUTION_URLS` | `false` |

Resolution links accept local absolute paths and HTTPS URLs. HTTP is available
only through its explicit compatibility switch; executable, protocol-relative,
malformed, and control-character URLs are discarded.

ActionGuard evaluates checks when the modal is rendered, when confirmation is
prepared, and immediately before execution. Checks must therefore be
side-effect-free and safe to run repeatedly. The final evaluation is decisive.

---

## 🧪 Testing & Quality Assurance

ActionGuard is built with strict quality standards:

```bash
# Run automated test suite (103 tests, 234 assertions)
# Run automated test suite (133 tests, 315 assertions)
composer test

# Run code style fixer and static analysis (PHPStan Level 8)
Expand All @@ -255,7 +291,9 @@ the optional data-minimised audit channel to your central logging system.

Filament ActionGuard was designed with data minimisation in mind. The package
does not transmit record data to external services and its optional audit events
exclude model attributes, record IDs, actor IDs, and check messages by default.
exclude model types, state values, record IDs, actor IDs, check messages, and
arbitrary model attributes by default. Model type and state can be enabled
independently when an application's documented audit purpose requires them.

Whether a deployment complies with the GDPR / DSGVO depends on the operator's
specific processing purposes, legal basis, access controls, logging and
Expand Down
26 changes: 26 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,32 @@
"suggest": {
"allgorithm/business-core": "Required only for the licensed Enterprise Bridge (^1.2); Community checks work without it."
},
"archive": {
"exclude": [
"/.ai",
"/.github",
"/art",
"/artisan",
"/build",
"/docker",
"/docker-compose.yml",
"/docs",
"/public",
"/requirements",
"/storage",
"/tests",
"/vendor",
"/workbench",
"/.DS_Store",
"/.env",
"/.env.example",
"/.gitattributes",
"/.gitignore",
"/phpstan.neon",
"/phpunit.xml.dist",
"/testbench.yaml"
]
},
"scripts": {
"post-autoload-dump": [
"@clear",
Expand Down
7 changes: 5 additions & 2 deletions config/filament-actionguard.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
'notifications' => (bool) env('ACTIONGUARD_NOTIFICATIONS_ENABLED', true),

/*
* The package never logs model attributes, messages, record IDs, or actor IDs.
* Applications may route this channel to their central audit system.
* Model types and state values are excluded by default and require separate,
* explicit opt-ins. Messages, record IDs, actor IDs, and arbitrary model
* attributes are never part of the package's audit context.
*/
'audit' => [
'enabled' => (bool) env('ACTIONGUARD_AUDIT_TRAIL', false),
'channel' => env('ACTIONGUARD_AUDIT_CHANNEL'),
'include_model_type' => (bool) env('ACTIONGUARD_AUDIT_INCLUDE_MODEL_TYPE', false),
'include_state' => (bool) env('ACTIONGUARD_AUDIT_INCLUDE_STATE', false),
],

/* Relative URLs and HTTPS are allowed; HTTP requires an explicit opt-in. */
Expand Down
9 changes: 6 additions & 3 deletions src/Actions/ActionGuardAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ protected function setUp(): void
$this->modalDescription(null);
$this->modalWidth('lg');

$rawLabel = $this->getLabel();
$label = $rawLabel instanceof Htmlable ? strip_tags($rawLabel->toHtml()) : (string) ($rawLabel ?? '');
$this->modalHeading(__('filament-actionguard::ui.modal.heading', ['label' => $label]));
$this->modalHeading(function (): string {
$rawLabel = $this->getLabel();
$label = $rawLabel instanceof Htmlable ? strip_tags($rawLabel->toHtml()) : (string) ($rawLabel ?? '');

return __('filament-actionguard::ui.modal.heading', ['label' => $label]);
});
$this->modalContent(function (ActionGuardAction $action, ?Model $record) {
return $action->evaluateAndRender($record);
});
Expand Down
Loading