Principle of least privilege — only grant access that is actually needed
Beginner5 min read·eng-19-011
interviewsecurity
Concept
Principle of Least Privilege (PoLP) — grant each component, user, or service ONLY the minimum permissions needed to perform its function. Nothing more.
Why PoLP matters:
- Limits blast radius: If an account is compromised, attackers can only do what that account was allowed to do.
- Reduces attack surface: Fewer permissions = fewer ways to be exploited.
- Contains mistakes: A developer bug that performs an unintended action is constrained by permissions.
Applied at different layers:
- Database users: App's DB user has
SELECT, INSERT, UPDATE, DELETE— notDROP TABLE,CREATE USER,GRANT. - File system: PHP-FPM runs as
www-datawith read access to app, write access only tostorage/andbootstrap/cache/. - API keys / service tokens: Scope tokens to only what's needed. A cron job that sends emails doesn't need permission to delete users.
- IAM roles (AWS): EC2 instance role has
s3:GetObjectands3:PutObjectfor your specific bucket — nots3:*on all buckets. - Laravel Gates: Admin gate requires explicit
is_adminflag — not "anyone logged in is an admin." - Sanctum token scopes: Mobile app token can
read-profile— can'tdelete-account.
PoLP examples in PHP:
- Database credentials:
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'%'— notGRANT ALL. - Queue workers: Only read from queue, dispatch events — not access user auth endpoints.
- Read-only reporting queries: Use a read-only DB replica connection with a read-only user.
Code Example
php
<?php
// SANCTUM TOKEN SCOPES — limit what a token can do
$token = $user->createToken('mobile-app', ['read-profile', 'create-order']);
// This token CANNOT: delete orders, read payment info, change password
Route::middleware(['auth:sanctum', 'abilities:read-profile'])->get('/profile', fn() => auth()->user());
Route::middleware(['auth:sanctum', 'abilities:create-order'])->post('/orders', [OrderController::class, 'store']);
Route::middleware(['auth:sanctum', 'abilities:delete-order'])->delete('/orders/{order}', ...);
// DELETE returns 403 — mobile-app token doesn't have delete-order ability
// DATABASE — least privilege
// MySQL: grant only what's needed
// GRANT SELECT, INSERT, UPDATE, DELETE ON `zira`.* TO 'zira_app'@'%';
// NOT: GRANT ALL PRIVILEGES ON *.* TO 'zira_app'@'%'
// For migrations (run separately, not with app user):
// GRANT ALTER, CREATE, DROP, INDEX ON `zira`.* TO 'zira_migrations'@'localhost';
// LARAVEL POLICIES — explicit per-action authorization
class OrderPolicy
{
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id; // only owner
}
public function delete(User $user, Order $order): bool
{
return $user->isAdmin(); // only admins can delete
// NOT: return true; (everyone) or return auth()->check(); (all authenticated)
}
public function refund(User $user, Order $order): bool
{
return $user->hasRole('billing'); // only billing role
}
}
// AWS IAM — least privilege role for Laravel app on EC2
// Role policy (only S3 access for the specific bucket):
/*
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}
// NOT: "Action": "s3:*", "Resource": "*"
*/
// FILESYSTEM — PHP-FPM should not own source files
// Run as www-data, source files owned by deploy user
// chmod 755 app/ bootstrap/ config/
// chmod 775 storage/ bootstrap/cache/ ← writable only where needed