darvis/livewire-injection-stopper
Livewire components expose public properties to the frontend JavaScript. While this enables reactive interfaces, it also creates a security risk if not properly managed.
Attackers can use browser developer tools or intercept HTTP requests to modify Livewire component properties:
// In browser console
Livewire.find('component-id').set('isAdmin', true);
Livewire.find('component-id').set('price', 0.01);
Any public property in a Livewire component can be modified by an attacker unless protected with #[Locked].
class CheckoutComponent extends Component
{
public float $totalPrice = 100.00;
public bool $isPremiumUser = false;
public function checkout()
{
$discount = $this->isPremiumUser ? 0.5 : 0;
$finalPrice = $this->totalPrice * (1 - $discount);
// Process payment for $finalPrice
}
}
// In browser console
Livewire.find('component-id').set('totalPrice', 0.01);
Livewire.find('component-id').set('isPremiumUser', true);
// Attacker pays $0.005 instead of $100
use Livewire\Attributes\Locked;
class CheckoutComponent extends Component
{
#[Locked]
public float $totalPrice = 100.00;
#[Locked]
public bool $isPremiumUser = false;
public function checkout()
{
// Properties cannot be manipulated!
$discount = $this->isPremiumUser ? 0.5 : 0;
$finalPrice = $this->totalPrice * (1 - $discount);
// Safe to process payment
}
}
// ❌ VULNERABLE
public bool $isAdmin = false;
public bool $canEdit = false;
// ✅ SECURE
#[Locked]
public bool $isAdmin = false;
#[Locked]
public bool $canEdit = false;
// ❌ VULNERABLE
public ?User $user = null;
public ?Cart $cart = null;
// ✅ SECURE
#[Locked]
public ?User $user = null;
#[Locked]
public ?Cart $cart = null;
// ❌ VULNERABLE
public int $maxQuantity = 10;
public float $discountRate = 0.1;
// ✅ SECURE
#[Locked]
public int $maxQuantity = 10;
#[Locked]
public float $discountRate = 0.1;
// ❌ VULNERABLE
public string $locale = 'en';
public bool $debugMode = false;
// ✅ SECURE
#[Locked]
public string $locale = 'en';
#[Locked]
public bool $debugMode = false;
Even with Livewire's #[Locked] attribute, always validate and sanitize data:
use Livewire\Attributes\Locked;
class UserProfileComponent extends Component
{
#[Locked]
public User $user;
public string $name = '';
public string $email = '';
public function save()
{
// Validate input
$validated = $this->validate([
'name' => 'required|string|max:255',
'email' => 'required|email',
]);
// Only update specific fields
$this->user->update([
'name' => $validated['name'],
'email' => $validated['email'],
]);
}
}
Before deploying a Livewire component:
#[Locked]Livewire.find('component-id').set('isAdmin', true);
If the property changes, it's vulnerable!
Run the security audit command:
php artisan livewire-injection-stopper:audit
If you discover a security vulnerability:
How can I help you explore Laravel packages today?