Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.

Commit c63819f

Browse files
committed
Security fixes
1 parent 2d84f2f commit c63819f

10 files changed

Lines changed: 452 additions & 20 deletions

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ public function panel(Panel $panel): Panel
5656
}
5757
```
5858
## Authorization
59-
To enforce policies on `ActivityResource`, after generating a policy, you would need to register `Spatie\Activitylog\Models\Activity` to use that policy in the AuthServiceProvider.
59+
`ActivityResource` now uses strict authorization by default. If there is no registered policy, or the policy does not implement `viewAny` / `view`, the log resource is denied instead of falling back to Filament's permissive default.
60+
61+
After generating a policy, register `Spatie\Activitylog\Models\Activity` to use that policy in the AuthServiceProvider.
6062
```php
6163
<?php
6264

@@ -77,6 +79,40 @@ class AuthServiceProvider extends ServiceProvider
7779
```
7880
> If you are using [Shield](https://filamentphp.com/plugins/shield) just register the ActivityPolicy generated by it
7981
82+
If you need the previous behavior for a legacy install, you can disable strict policy enforcement:
83+
84+
```php
85+
'authorization' => [
86+
'strict' => false,
87+
],
88+
```
89+
90+
## Security defaults
91+
This package now applies a few safer defaults out of the box:
92+
93+
- `ActivityResource` requires explicit policy methods when `authorization.strict` is enabled.
94+
- Sensitive keys such as passwords, tokens, secrets, and recovery codes are redacted before being stored in activity properties.
95+
- Access logs anonymize IP addresses and trim user agents by default.
96+
- Notification recipients are not logged unless `notifications.log_recipient` is explicitly enabled.
97+
98+
Example overrides:
99+
100+
```php
101+
'redacted_placeholder' => '[REDACTED]',
102+
103+
'access' => [
104+
'store_ip' => true,
105+
'anonymize_ip' => true,
106+
'store_user_agent' => true,
107+
'user_agent_max_length' => 255,
108+
],
109+
110+
'notifications' => [
111+
'log_recipient' => false,
112+
'mask_recipient' => true,
113+
],
114+
```
115+
80116
## Translations
81117
Publish the translations using:
82118

config/filament-logger.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,29 @@
22
return [
33
'datetime_format' => 'd/m/Y H:i:s',
44
'date_format' => 'd/m/Y',
5+
'redacted_placeholder' => '[REDACTED]',
6+
7+
'authorization' => [
8+
'strict' => true,
9+
],
10+
11+
'sensitive_keys' => [
12+
'password',
13+
'password_confirmation',
14+
'current_password',
15+
'secret',
16+
'client_secret',
17+
'api_key',
18+
'private_key',
19+
'token',
20+
'api_token',
21+
'access_token',
22+
'refresh_token',
23+
'remember_token',
24+
'two_factor_secret',
25+
'two_factor_recovery_codes',
26+
'recovery_codes',
27+
],
528

629
'activity_resource' => \MrAdder\FilamentLogger\Resources\ActivityResource::class,
730
'scoped_to_tenant' => true,
@@ -25,13 +48,19 @@
2548
'logger' => \MrAdder\FilamentLogger\Loggers\AccessLogger::class,
2649
'color' => 'danger',
2750
'log_name' => 'Access',
51+
'store_ip' => true,
52+
'anonymize_ip' => true,
53+
'store_user_agent' => true,
54+
'user_agent_max_length' => 255,
2855
],
2956

3057
'notifications' => [
3158
'enabled' => true,
3259
'logger' => \MrAdder\FilamentLogger\Loggers\NotificationLogger::class,
3360
'color' => null,
3461
'log_name' => 'Notification',
62+
'log_recipient' => false,
63+
'mask_recipient' => true,
3564
],
3665

3766
'models' => [

src/Loggers/AbstractModelLogger.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Contracts\Auth\Authenticatable;
88
use Illuminate\Database\Eloquent\Model;
99
use Illuminate\Support\Str;
10+
use MrAdder\FilamentLogger\Support\LogDataSanitizer;
1011
use Spatie\Activitylog\ActivityLogger;
1112
use Spatie\Activitylog\ActivityLogStatus;
1213

@@ -54,7 +55,7 @@ protected function getLoggableAttributes(Model $model, mixed $values = []): arra
5455
$values = array_diff_key($values, array_flip($model->getHidden()));
5556
}
5657

57-
return $values;
58+
return LogDataSanitizer::sanitizeProperties($values);
5859
}
5960

6061
protected function log(Model $model, string $event, ?string $description = null, mixed $attributes = null)

src/Loggers/AccessLogger.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Filament\Facades\Filament;
66
use Illuminate\Auth\Events\Login;
7+
use MrAdder\FilamentLogger\Support\LogDataSanitizer;
78
use Spatie\Activitylog\ActivityLogger;
89
use Spatie\Activitylog\ActivityLogStatus;
910

@@ -18,12 +19,18 @@ class AccessLogger
1819
public function handle(Login $event)
1920
{
2021
$description = Filament::getUserName($event->user).' logged in';
22+
$properties = LogDataSanitizer::sanitizeProperties([
23+
'ip' => request()->ip(),
24+
'user_agent' => request()->userAgent(),
25+
]);
26+
27+
$properties = array_filter($properties, fn (mixed $value): bool => filled($value));
2128

2229
app(ActivityLogger::class)
2330
->useLog(config('filament-logger.access.log_name'))
2431
->setLogStatus(app(ActivityLogStatus::class))
25-
->withProperties(['ip' => request()->ip(), 'user_agent' => request()->userAgent()])
32+
->withProperties($properties)
2633
->event('Login')
2734
->log($description);
2835
}
29-
}
36+
}

src/Loggers/NotificationLogger.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22

33
namespace MrAdder\FilamentLogger\Loggers;
44

5-
use Illuminate\Notifications\AnonymousNotifiable;
65
use Illuminate\Notifications\Events\NotificationFailed;
76
use Illuminate\Notifications\Events\NotificationSent;
8-
use Illuminate\Notifications\Notifiable;
7+
use MrAdder\FilamentLogger\Support\LogDataSanitizer;
98
use Illuminate\Support\Str;
109
use Spatie\Activitylog\ActivityLogStatus;
1110
use Spatie\Activitylog\ActivityLogger;
@@ -28,10 +27,12 @@ public function handle(NotificationSent|NotificationFailed $event)
2827
$description = $notification.' Notification failed';
2928
}
3029

31-
$receipent = $this->getRecipient($event->notifiable, $event->channel);
32-
33-
if($receipent) {
34-
$description .= ' to '.$receipent;
30+
$recipient = LogDataSanitizer::sanitizeNotificationRecipient(
31+
$this->getRecipient($event->notifiable, $event->channel)
32+
);
33+
34+
if ($recipient) {
35+
$description .= ' to '.$recipient;
3536
}
3637

3738
app(ActivityLogger::class)
@@ -45,6 +46,7 @@ public function handle(NotificationSent|NotificationFailed $event)
4546
public function getRecipient(mixed $notifiable, string $channel): ?string
4647
{
4748
$notificationRoute = $notifiable->routeNotificationFor($channel);
49+
4850
return is_string($notificationRoute) ? $notificationRoute : null;
4951
}
5052
}

src/Resources/ActivityResource.php

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Spatie\Activitylog\Contracts\Activity;
2323
use Spatie\Activitylog\ActivitylogServiceProvider;
2424
use Spatie\Activitylog\Models\Activity as ActivityModel;
25+
use Illuminate\Support\Facades\Gate;
26+
use MrAdder\FilamentLogger\Support\LogDataSanitizer;
2527
use MrAdder\FilamentLogger\Resources\ActivityResource\Pages;
2628

2729
class ActivityResource extends Resource
@@ -37,6 +39,29 @@ public static function getCluster(): ?string
3739
return config('filament-logger.resources.cluster');
3840
}
3941

42+
public static function canAccess(): bool
43+
{
44+
return static::canViewAny();
45+
}
46+
47+
public static function canViewAny(): bool
48+
{
49+
if (! static::hasRequiredPolicyAbility('viewAny')) {
50+
return false;
51+
}
52+
53+
return parent::canViewAny();
54+
}
55+
56+
public static function canView(Model $record): bool
57+
{
58+
if (! static::hasRequiredPolicyAbility('view')) {
59+
return false;
60+
}
61+
62+
return parent::canView($record);
63+
}
64+
4065
public static function form(Form $form): Form
4166
{
4267
return $form
@@ -92,26 +117,39 @@ public static function form(Form $form): Form
92117
]),
93118
Section::make()
94119
->columns()
95-
->visible(fn ($record) => $record->properties?->count() > 0)
120+
->visible(function (?Model $record): bool {
121+
if (! $record instanceof ActivityModel) {
122+
return false;
123+
}
124+
125+
return $record->properties->count() > 0;
126+
})
96127
->schema(function (?Model $record) {
128+
if (! $record instanceof ActivityModel) {
129+
return [];
130+
}
131+
97132
/** @var Activity&ActivityModel $record */
98-
$properties = $record->properties->except(['attributes', 'old']);
133+
$properties = LogDataSanitizer::sanitizeProperties(
134+
$record->properties->except(['attributes', 'old'])
135+
);
99136

100137
$schema = [];
101138

102-
if ($properties->count()) {
139+
if (count($properties) > 0) {
103140
$schema[] = KeyValue::make('properties')
141+
->afterStateHydrated(fn (KeyValue $component) => $component->state($properties))
104142
->label(__('filament-logger::filament-logger.resource.label.properties'))
105143
->columnSpan('full');
106144
}
107145

108-
if ($old = $record->properties->get('old')) {
146+
if ($old = LogDataSanitizer::sanitizeProperties($record->properties->get('old') ?? [])) {
109147
$schema[] = KeyValue::make('old')
110148
->afterStateHydrated(fn (KeyValue $component) => $component->state($old))
111149
->label(__('filament-logger::filament-logger.resource.label.old'));
112150
}
113151

114-
if ($attributes = $record->properties->get('attributes')) {
152+
if ($attributes = LogDataSanitizer::sanitizeProperties($record->properties->get('attributes') ?? [])) {
115153
$schema[] = KeyValue::make('attributes')
116154
->afterStateHydrated(fn (KeyValue $component) => $component->state($attributes))
117155
->label(__('filament-logger::filament-logger.resource.label.new'));
@@ -175,7 +213,7 @@ public static function table(Table $table): Table
175213

176214
Filter::make('properties->old')
177215
->indicateUsing(function (array $data): ?string {
178-
if (!$data['old']) {
216+
if (! ($data['old'] ?? null)) {
179217
return null;
180218
}
181219

@@ -187,7 +225,7 @@ public static function table(Table $table): Table
187225
->hint(__('filament-logger::filament-logger.resource.label.properties_hint')),
188226
])
189227
->query(function (Builder $query, array $data): Builder {
190-
if (!$data['old']) {
228+
if (! ($data['old'] ?? null)) {
191229
return $query;
192230
}
193231

@@ -196,7 +234,7 @@ public static function table(Table $table): Table
196234

197235
Filter::make('properties->attributes')
198236
->indicateUsing(function (array $data): ?string {
199-
if (!$data['new']) {
237+
if (! ($data['new'] ?? null)) {
200238
return null;
201239
}
202240

@@ -208,7 +246,7 @@ public static function table(Table $table): Table
208246
->hint(__('filament-logger::filament-logger.resource.label.properties_hint')),
209247
])
210248
->query(function (Builder $query, array $data): Builder {
211-
if (!$data['new']) {
249+
if (! ($data['new'] ?? null)) {
212250
return $query;
213251
}
214252

@@ -348,5 +386,14 @@ public static function getNavigationSort(): ?int
348386
return config('filament-logger.navigation_sort', null);
349387
}
350388

351-
389+
protected static function hasRequiredPolicyAbility(string $ability): bool
390+
{
391+
if (! config('filament-logger.authorization.strict', true)) {
392+
return true;
393+
}
394+
395+
$policy = Gate::getPolicyFor(static::getModel());
396+
397+
return ($policy !== null) && method_exists($policy, $ability);
398+
}
352399
}

0 commit comments

Comments
 (0)