n3xt0r/filament-lockbox

Filament v4 security addon to protect sensitive data with user-bound encryption keys (Split-Key, TOTP, or crypto password)

Fund package maintenance!
N3XT0R

1.0.0-alpha 2025-09-20 14:27 UTC

README

CI Security Rating Maintainability Code Coverage Latest Stable Version License CodeRabbit Pull Request Reviews

Filament Lockbox

Filament Lockbox Logo

Secure per-user field encryption for Filament v4.
This package allows you to encrypt and decrypt sensitive data on a per-user basis, using a split-key approach:

  • Part A (server-side key) is stored encrypted in the database.
  • Part B (user-provided secret) is collected at runtime (crypto password, passkey, or TOTP).
  • Final key is derived from PartA + PartB using hash('sha256', ...).

This ensures that even administrators cannot decrypt data without the user-provided input.

🚧 Project Status

This package is currently in alpha and under active development. Features and APIs may change before a stable release.

✨ Features

  • πŸ”‘ Per-user encryption keys (split key: server + user)
  • 🧩 Plug-and-play Filament components:
    • EncryptedTextInput β†’ encrypts before save
    • DecryptedTextDisplay β†’ decrypts on display
    • UnlockLockboxAction β†’ prompts for crypto password or TOTP
  • πŸ”’ User-configurable crypto password support
  • πŸ—οΈ Passkey (WebAuthn) support if your user implements HasPasskeys
  • πŸ” TOTP support if your user implements HasAppAuthentication
  • πŸ›‘οΈ Zero-knowledge for admins – data is unreadable without user input
  • βš™οΈ Configurable key material providers (PBKDF2, Passkeys, TOTP, custom)

πŸ—„οΈ Centralized Lockbox Storage

Unlike typical field encryption solutions, Filament Lockbox does not store encrypted data on your models.
Instead, all encrypted values are kept in a dedicated, polymorphic lockbox table β€” completely transparent to your application.

βœ… Benefits of This Architecture

  • Drop-in Usage
    Simply use EncryptedTextInput anywhere in your Filament form schema β€” no schema changes or model attributes required.

  • Polymorphic & Universal
    Works with any Eloquent model (User, Product, Order, ...).
    All sensitive data is centralized, making it easy to see which records have encrypted fields.

  • Performance-Friendly
    Main tables remain lean and fast, as encrypted data is kept out of your core business tables.

  • Compliance & Auditing

    • Simplified GDPR / β€œRight to be Forgotten”: just delete Lockbox entries per user.
    • Perfect for audits: one table gives full visibility of all encrypted fields.
    • Allows separate backup and retention strategies.
  • Developer Experience

    • No manual hooks or closures needed β€” saving & loading is handled automatically.
    • dehydrated(false) is applied internally.
    • Just replace TextInput with EncryptedTextInput and get full encryption.
$form->schema([
    // Before:
    TextInput::make('credit_card'),

    // After:
    EncryptedTextInput::make('credit_card')
        ->label('Credit Card'),
]);

The plugin takes care of everything:

  • πŸ”‘ Per-user key management
  • πŸ” Encryption & decryption
  • πŸ—„οΈ Transparent Lockbox record handling
  • πŸ”„ Auto-loading of values on form display
  • 🧹 Automatic cleanup when models are deleted

πŸ”‘ How It Works (Key Derivation)

           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚  encrypted_user_key    β”‚  (in DB, encrypted with APP_KEY)
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚ decrypt
                      β–Ό
                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                 β”‚  Part A  β”‚  (server key)
                 β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                       β”‚
                       β”‚
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚  Part B (User Input) β”‚  ← crypto password, passkey, or TOTP
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ combine
                       β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  Final Key (32B)  β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚ Encrypt / Decrypt fields β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

This means database leaks alone cannot decrypt your data – PartB must be provided by the user.

πŸš€ Installation

Install the package via Composer:

composer require n3xt0r/filament-lockbox

Important:
This package integrates with spatie/laravel-passkeys. Before running the install command, make sure you have published and run the Spatie migrations:

php artisan vendor:publish --provider="Spatie\\LaravelPasskeys\\LaravelPasskeysServiceProvider" --tag="laravel-passkeys-migrations"
php artisan migrate
Run the install command to publish all required assets and migrations:

```bash
php artisan filament-lockbox:install

πŸ”Œ Register the Plugin (Filament v4)

Add the plugin to your Filament panel provider:

// app/Providers/Filament/AdminPanelProvider.php

use Filament\Panel;
use Filament\PanelProvider;
use N3XT0R\FilamentLockbox\FilamentLockboxPlugin;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->plugins([
                FilamentLockboxPlugin::make(),
            ]);
    }
}

Optional configuration:

// config/filament-lockbox.php
return [
    'show_widget' => true, // set false to hide the status widget
    'providers' => [
        \N3XT0R\FilamentLockbox\Managers\KeyMaterial\TotpKeyMaterialProvider::class,
        \N3XT0R\FilamentLockbox\Managers\KeyMaterial\CryptoPasswordKeyMaterialProvider::class,
    ],
];

You can publish the config and translations if you need customization:

php artisan vendor:publish --tag="filament-lockbox-config"
php artisan vendor:publish --tag="filament-lockbox-translations"

βš™οΈ Model Setup

Your User model must:

  • Implement HasLockboxKeys
  • Use the InteractsWithLockboxKeys trait
  • Hide and cast the lockbox fields

Example: User Model

use Filament\Models\Contracts\FilamentUser;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use N3XT0R\FilamentLockbox\Contracts\HasLockboxKeys;
use N3XT0R\FilamentLockbox\Concerns\InteractsWithLockboxKeys;

class User extends Authenticatable implements FilamentUser, MustVerifyEmail, HasLockboxKeys
{
    use InteractsWithLockboxKeys;

    protected $hidden = [
        'encrypted_user_key',
        'crypto_password_hash',
        'lockbox_provider',
    ];

    protected function casts(): array
    {
        return [
            'encrypted_user_key' => 'encrypted',
            'crypto_password_hash' => 'string',
            'lockbox_provider' => 'string',
        ];
    }
}

Example: Any Model with Encrypted Fields

Any Eloquent model that should have encrypted fields must:

  • Implement HasLockbox
  • Use the InteractsWithLockbox trait

This enables the polymorphic relation to the lockbox table and lets the package handle encryption transparently.

use Illuminate\Database\Eloquent\Model;
use N3XT0R\FilamentLockbox\Contracts\HasLockbox;
use N3XT0R\FilamentLockbox\Concerns\InteractsWithLockbox;

class Company extends Model implements HasLockbox
{
    use InteractsWithLockbox;

    protected $fillable = [
        'name',
        'email',
        // no need to list encrypted fields here – they live in the lockbox table
    ];
}

You can now use EncryptedTextInput::make('field_name') in your Filament form schemas for this model β€”
the package will automatically store and retrieve the data from the centralized lockbox table.

πŸ§‘β€πŸ’» User Flow

  1. Go to the Lockbox widget in your Filament panel.
  2. Click Generate Lockbox Key.
  3. Set a crypto password, register a passkey, or enable TOTP.
  4. Unlock once per session to access or modify encrypted fields.

🧩 Usage in Filament Forms

1️⃣ Storing Encrypted Data

use N3XT0R\FilamentLockbox\Forms\Actions\UnlockLockboxAction;
use N3XT0R\FilamentLockbox\Forms\Components\EncryptedTextInput;

$form
    ->schema([
        EncryptedTextInput::make('secret_notes')
            ->label('Secret Notes'),
    ])
    ->extraActions([
        UnlockLockboxAction::make(),
    ]);

2️⃣ Displaying Decrypted Data

use N3XT0R\FilamentLockbox\Forms\Components\DecryptedTextDisplay;
use N3XT0R\FilamentLockbox\Forms\Actions\UnlockLockboxAction;

$form
    ->schema([
        DecryptedTextDisplay::make('secret_notes')
            ->label('Secret Notes'),
    ])
    ->extraActions([
        UnlockLockboxAction::make(),
    ]);

πŸ”’ Security Model

  • Split-key encryption (PartA + PartB β†’ Final Key)
  • PBKDF2 key derivation with 100,000 iterations
  • Server keys stored encrypted with APP_KEY
  • Extensible providers for alternative key material

πŸ”‘ Passkeys (WebAuthn)

This package ships with built-in support for spatie/laravel-passkeys and requires it by default.

You can control Passkey usage via this package's configuration. If you don't plan to use WebAuthn/Passkeys, disable the integration in config/filament-lockbox.php.

πŸ“– Roadmap

  • Textarea and file encryption support
  • Automatic modal prompt if unlock is missing
  • Session-based unlock expiry
  • Configurable PBKDF2 parameters

πŸ“œ License

MIT Β© N3XT0R