Overview
The Principle of Least Privilege (PoLP) dictates that entities within a system receive only the permissions necessary to accomplish their specific tasks, no more and no less. This foundational security principle emerged from military access control systems in the 1970s and became formalized in computer security through the work of Jerome Saltzer and Michael Schroeder in their 1975 paper on protection mechanisms.
The principle applies across multiple layers of software systems: operating system processes, database connections, API endpoints, user roles, file system permissions, and network access controls. Each component or actor receives a minimal set of privileges that allows function execution without granting unnecessary capabilities that could be exploited.
PoLP reduces the attack surface of a system by limiting potential damage from compromised components. When a process runs with excessive privileges, successful exploitation grants attackers broader system access. Conversely, properly restricted privileges contain breaches within narrow boundaries.
# Excessive privileges - process runs as root
system("rm -rf /tmp/cache/*") # Dangerous if exploited
# Least privilege - process runs with specific user permissions
require 'fileutils'
FileUtils.rm_rf(Dir.glob('/tmp/cache/*'), secure: true)
The principle intersects with separation of duties, defense in depth, and the security principle of fail-safe defaults. Systems designed with PoLP naturally compartmentalize functions, making security boundaries explicit and enforceable. This compartmentalization enables fine-grained auditing, since each privilege boundary creates a logging point for monitoring access patterns and detecting anomalies.
Key Principles
The Principle of Least Privilege rests on several interconnected concepts that define its scope and application. These principles establish how permissions should be granted, validated, and constrained throughout a system's lifecycle.
Minimal Permission Sets: Each entity receives exactly the permissions required for its designated operations. This requires identifying the specific resources and operations needed, then granting only those capabilities. Determining minimal permissions involves analyzing the entity's function, dependencies, and data access patterns. Overly broad permissions violate this principle even if they appear convenient during development.
Temporal Restriction: Privileges should be granted for the minimum duration necessary. Rather than permanent elevated access, systems implement time-bound permissions that expire after task completion. This temporal aspect prevents privilege accumulation where entities retain access long after the need disappears.
Contextual Access: Permission grants consider the operational context including time of day, request origin, data sensitivity, and user activity patterns. A database connection might have read-only access during batch processing but require write access during transaction processing. Context-aware restrictions adapt privilege levels to match legitimate usage patterns.
Separation of Privileges: Complex operations decompose into discrete steps, each executed with appropriate privileges. A deployment process might separate code retrieval (low privilege), compilation (medium privilege), and system installation (high privilege). This separation ensures compromise at one stage doesn't automatically grant access to other stages.
# Separation of privileges in file processing
class FileProcessor
def process(file_path)
content = read_file(file_path) # Read-only operation
result = transform(content) # No file system access
write_file(result, file_path) # Write-only operation
end
private
def read_file(path)
# Opens file with read-only mode
File.open(path, 'r', &:read)
end
def transform(content)
# Pure transformation, no I/O
content.upcase
end
def write_file(content, path)
# Opens file with write-only mode
File.open(path, 'w') { |f| f.write(content) }
end
end
Privilege Escalation Controls: Systems must strictly control transitions from lower to higher privilege levels. Escalation requires explicit authorization, authentication, and audit logging. Many security breaches exploit uncontrolled privilege escalation where lower-privilege contexts gain elevated access through vulnerabilities.
Revocation Mechanisms: The ability to revoke privileges proves as important as granting them. Systems need immediate privilege revocation when threats emerge, employees change roles, or credentials become compromised. Revocation must be atomic and complete, leaving no residual access through cached credentials or lingering sessions.
Accountability and Auditing: Each privilege use requires logging and attribution. Audit trails record which entity accessed what resource, when, and with which privileges. This accountability deters misuse and enables forensic analysis after security incidents.
Security Implications
The Principle of Least Privilege directly impacts system security posture by constraining the potential damage from security breaches, insider threats, and software defects. Understanding these implications guides implementation decisions and helps prioritize security investments.
Attack Surface Reduction: Every unnecessary privilege represents a potential attack vector. Systems running with excessive privileges give attackers more capabilities upon successful exploitation. A web application running as root can modify system files, create new user accounts, or install persistent backdoors. The same application running as a restricted user can only access its designated data directories.
# Insecure: Running with unnecessary privileges
class DatabaseMigration
def execute
# Running as superuser can modify any database
connection = PG.connect(dbname: 'production', user: 'postgres')
connection.exec('DROP DATABASE backup') # Dangerous capability
end
end
# Secure: Restricted to necessary operations
class DatabaseMigration
def execute
# Running as migration user with limited grants
connection = PG.connect(dbname: 'production', user: 'migration_user')
connection.exec('CREATE TABLE users (id serial)') # Only permitted operations
end
end
Lateral Movement Prevention: In multi-tier architectures, least privilege limits lateral movement between system components. When an attacker compromises one service, restrictive permissions prevent using that foothold to pivot to other services. Network segmentation combined with application-level privilege restrictions creates defense in depth that contains breaches.
Credential Exposure Risks: Systems storing credentials with excessive scope amplify breach impact. An API key with full account access creates higher risk than multiple purpose-specific keys. When credentials leak through logs, error messages, or configuration files, least privilege minimizes the damage.
# High risk: Single credential with broad access
class APIClient
def initialize
@api_key = ENV['ADMIN_API_KEY'] # Full account access
end
def fetch_users
HTTP.auth("Bearer #{@api_key}").get('/users')
end
def delete_user(id)
HTTP.auth("Bearer #{@api_key}").delete("/users/#{id}")
end
end
# Lower risk: Separate credentials per function
class APIClient
def fetch_users
api_key = ENV['READ_ONLY_API_KEY']
HTTP.auth("Bearer #{api_key}").get('/users')
end
def delete_user(id)
api_key = ENV['WRITE_API_KEY']
HTTP.auth("Bearer #{api_key}").delete("/users/#{id}")
end
end
Insider Threat Mitigation: Least privilege restricts damage from malicious insiders or compromised employee accounts. An engineer's credentials should not grant access to production databases, financial records, or customer data unrelated to their work. Role-based access controls (RBAC) implementing least privilege prevent single accounts from accessing all system components.
Compliance Requirements: Regulations like GDPR, HIPAA, and SOC 2 mandate access controls based on least privilege. Audit requirements demand demonstrable evidence that systems restrict access to the minimum necessary. Compliance frameworks require documented processes for privilege grants, periodic access reviews, and automated privilege revocation.
Supply Chain Security: Third-party integrations and dependencies should operate with minimal privileges. A logging library needs append-only file access, not read/write/execute permissions across the file system. Cloud service integrations require specific API scopes rather than administrative access. This restriction limits damage from compromised dependencies.
Data Breach Containment: When data breaches occur, least privilege determines the scope of exposed information. A compromised service with read-only access to a single table limits exposure compared to full database access. Granular permissions at the data layer create containment boundaries that reduce breach severity.
Ruby Implementation
Ruby provides multiple mechanisms for implementing least privilege across different layers of application security. Understanding these capabilities enables developers to build systems with appropriate privilege restrictions.
File System Permissions: Ruby's File class respects and enforces operating system file permissions. Opening files with explicit mode specifications restricts operations to the minimum required.
# Read-only file access
File.open('config.yml', 'r') do |file|
config = YAML.load(file.read)
end
# Write-only with creation, no read capability
File.open('log.txt', 'a') do |file|
file.write("#{Time.now}: Event logged\n")
end
# Explicitly restrict permissions on created files
File.open('secrets.txt', 'w', 0600) do |file|
file.write(secret_data)
end
# File created with permissions 0600 (owner read/write only)
Process User Context: Ruby applications inherit the permissions of the user executing the process. Systems should run application processes as dedicated users with minimal privileges rather than root or administrative accounts.
# Check current process permissions
def check_privileges
uid = Process.uid
gid = Process.gid
euid = Process.euid
if euid == 0
raise SecurityError, "Application running as root - privilege escalation risk"
end
puts "Running as UID: #{uid}, GID: #{gid}"
end
# Drop privileges after startup (requires root initially)
def drop_privileges(username)
user_info = Etc.getpwnam(username)
Process::Sys.setgid(user_info.gid)
Process::Sys.setuid(user_info.uid)
# Verify privileges dropped
raise SecurityError unless Process.uid == user_info.uid
end
Database Connection Restrictions: Database libraries support connection-level privilege restrictions through user accounts and connection parameters.
# Read-only database connection
class ReportGenerator
def initialize
@connection = PG.connect(
dbname: 'analytics',
user: 'readonly_user',
password: ENV['READONLY_PASSWORD']
)
end
def generate_report
@connection.exec('SELECT * FROM reports')
end
# This will fail with permission denied
def dangerous_operation
@connection.exec('DELETE FROM reports')
end
end
# Separate connections for different privilege levels
class UserManager
def read_connection
@read_conn ||= PG.connect(
dbname: 'users',
user: 'read_user',
password: ENV['READ_PASSWORD']
)
end
def write_connection
@write_conn ||= PG.connect(
dbname: 'users',
user: 'write_user',
password: ENV['WRITE_PASSWORD']
)
end
def list_users
read_connection.exec('SELECT id, name FROM users')
end
def create_user(name)
write_connection.exec_params(
'INSERT INTO users (name) VALUES ($1)',
[name]
)
end
end
API Client Scoping: HTTP clients can implement least privilege through scope-limited authentication tokens and request restrictions.
class GitHubClient
# Separate clients with different token scopes
def self.read_client
@read_client ||= Octokit::Client.new(
access_token: ENV['GITHUB_READ_TOKEN'] # repo:status, public_repo
)
end
def self.write_client
@write_client ||= Octokit::Client.new(
access_token: ENV['GITHUB_WRITE_TOKEN'] # repo
)
end
def list_repositories
self.class.read_client.repositories
end
def create_repository(name)
self.class.write_client.create_repository(name)
end
end
Capability-Based Security: Ruby objects can implement capability patterns where references themselves represent authority.
# Capability object that grants specific operations
class FileCapability
def initialize(path, operations)
@path = path
@operations = operations
end
def read
raise SecurityError unless @operations.include?(:read)
File.read(@path)
end
def write(content)
raise SecurityError unless @operations.include?(:write)
File.write(@path, content)
end
def delete
raise SecurityError unless @operations.include?(:delete)
File.delete(@path)
end
end
# Grant minimum capabilities
def process_file
capability = FileCapability.new('/data/file.txt', [:read])
content = capability.read
# capability.write('new') would raise SecurityError
content
end
Sandboxing with $SAFE: Older Ruby versions provided $SAFE levels for sandboxing untrusted code. Modern Ruby removed this feature, but the concept illustrates privilege restriction for code execution.
# Modern alternative: Restrict available methods
class SandboxedExecutor
ALLOWED_METHODS = [:+, :-, :*, :/, :to_s, :to_i].freeze
def execute(code, context)
context.instance_eval do
def method_missing(method, *args)
unless ALLOWED_METHODS.include?(method)
raise SecurityError, "Method #{method} not allowed"
end
super
end
eval(code)
end
end
end
executor = SandboxedExecutor.new
executor.execute("2 + 2", binding) # Works
executor.execute("system('ls')", binding) # Raises SecurityError
Environment Variable Restrictions: Limit access to sensitive environment variables through wrapper classes.
class RestrictedEnv
ALLOWED_VARS = ['APP_NAME', 'LOG_LEVEL', 'PORT'].freeze
def self.[](key)
unless ALLOWED_VARS.include?(key)
raise SecurityError, "Access to #{key} not permitted"
end
ENV[key]
end
end
# Use restricted environment
app_name = RestrictedEnv['APP_NAME'] # Works
secret = RestrictedEnv['API_SECRET'] # Raises SecurityError
Design Considerations
Implementing least privilege requires balancing security requirements with operational needs, development velocity, and system complexity. Design decisions affect both security posture and maintainability.
Granularity Trade-offs: Fine-grained permissions provide better security isolation but increase management overhead. A system with hundreds of specific permissions requires more complex authorization logic, increases configuration surface area, and complicates debugging. Coarse-grained permissions simplify management but grant broader access than strictly necessary.
# Fine-grained approach
class PermissionManager
PERMISSIONS = {
'user.read.own' => 'Read own user data',
'user.read.team' => 'Read team user data',
'user.read.all' => 'Read all user data',
'user.update.own' => 'Update own user data',
'user.update.team' => 'Update team user data',
'user.update.all' => 'Update all user data',
'user.delete.own' => 'Delete own user data',
'user.delete.all' => 'Delete all user data'
}
def authorize(user, permission)
user.permissions.include?(permission)
end
end
# Coarse-grained approach
class SimplePermissionManager
ROLES = {
'viewer' => ['read'],
'editor' => ['read', 'write'],
'admin' => ['read', 'write', 'delete']
}
def authorize(user, action)
ROLES[user.role]&.include?(action)
end
end
The optimal granularity depends on data sensitivity, regulatory requirements, and organizational structure. Financial systems handling monetary transactions require fine-grained controls, while internal tools may function adequately with role-based permissions.
Performance Implications: Authorization checks add latency to request processing. Systems performing authorization queries on every operation may experience performance degradation. Caching permission grants reduces overhead but introduces consistency challenges when permissions change.
# Authorization with caching
class CachedAuthorization
def initialize
@cache = {}
@ttl = 300 # 5 minute cache
end
def authorize(user_id, resource_id, action)
cache_key = "#{user_id}:#{resource_id}:#{action}"
if cached = @cache[cache_key]
return cached[:result] if Time.now - cached[:timestamp] < @ttl
end
result = perform_authorization_check(user_id, resource_id, action)
@cache[cache_key] = { result: result, timestamp: Time.now }
result
end
private
def perform_authorization_check(user_id, resource_id, action)
# Expensive database query
DB[:permissions].where(
user_id: user_id,
resource_id: resource_id,
action: action
).any?
end
end
Privilege Escalation Workflows: Some operations legitimately require elevated privileges. Design patterns for temporary escalation include sudo-style authentication, approval workflows, and time-limited tokens. Each pattern involves trade-offs between security and usability.
# Temporary privilege escalation with approval
class PrivilegeEscalation
def request_elevated_access(user, operation, duration)
request = EscalationRequest.create(
user: user,
operation: operation,
expires_at: Time.now + duration,
status: 'pending'
)
notify_approvers(request)
request
end
def execute_with_elevation(request, &block)
unless request.approved? && request.expires_at > Time.now
raise SecurityError, "Elevation not authorized"
end
# Execute with elevated context
with_elevated_privileges(request.user) do
block.call
end
ensure
log_elevated_action(request)
end
private
def with_elevated_privileges(user)
original_privileges = user.privileges
user.privileges = :elevated
yield
ensure
user.privileges = original_privileges
end
end
Default Deny vs Default Allow: Security policies default to denying access unless explicitly granted, or default to allowing access unless explicitly denied. Default deny aligns with least privilege but requires enumerating all legitimate access patterns. Default allow simplifies initial development but creates security gaps.
Separation of Concerns: Privilege logic should separate from business logic. Mixing authorization checks throughout application code creates maintenance burden and increases the likelihood of security gaps. Centralized authorization systems provide consistency and auditability.
# Mixed concerns (problematic)
class UserController
def update
user = User.find(params[:id])
# Authorization mixed with business logic
if current_user.admin? || current_user.id == user.id
user.update(user_params)
else
raise Unauthorized
end
end
end
# Separated concerns (preferred)
class UserController
before_action :authorize_update
def update
user = User.find(params[:id])
user.update(user_params)
end
private
def authorize_update
authorize User.find(params[:id]), :update?
end
end
class UserPolicy
def update?
user.admin? || user.id == record.id
end
end
Dynamic vs Static Privileges: Static privilege assignments define access at deployment time through configuration. Dynamic privileges compute access based on runtime state like data attributes, user relationships, or temporal conditions. Dynamic privileges provide flexibility but increase complexity.
Common Pitfalls
Developers frequently make mistakes when implementing least privilege, often due to convenience, misunderstanding, or incomplete security awareness. Recognizing these patterns helps avoid security vulnerabilities.
Privilege Creep: Permissions accumulate over time as features are added without removing obsolete access grants. A service initially needing database read access might later require write access for a new feature. Developers add write permission but forget to restrict it to specific operations, leaving the service with broader access than needed.
# Before: Read-only access
class ReportService
def initialize
@db = Database.connect(user: 'readonly')
end
end
# After: Added write for caching, but granted full write access
class ReportService
def initialize
@db = Database.connect(user: 'write_user') # Too broad
end
def cache_report(report)
@db.insert(:cached_reports, report)
end
end
# Correct: Separate connections for different needs
class ReportService
def read_connection
@read_conn ||= Database.connect(user: 'readonly')
end
def cache_connection
@cache_conn ||= Database.connect(user: 'cache_writer')
end
def cache_report(report)
cache_connection.insert(:cached_reports, report)
end
end
Development Convenience Shortcuts: Running applications with administrative privileges during development creates security habits that persist into production. Developers accustomed to unrestricted access may not notice missing permission checks until production deployment.
Overly Broad Database Grants: Database users often receive GRANT ALL or administrative privileges for simplicity. Applications should use database users with specific grants limited to required tables and operations.
# Problematic: Single database user with all privileges
DATABASE_USER = 'app_admin' # Has GRANT ALL
# Better: Multiple users with specific grants
class DatabaseConnections
def read_only
PG.connect(user: 'app_reader') # SELECT only on application tables
end
def transactional
PG.connect(user: 'app_writer') # INSERT, UPDATE, DELETE on data tables
end
def migrations
PG.connect(user: 'app_migrations') # DDL operations only
end
end
Token Scope Neglect: API tokens and credentials often have broader scopes than necessary. Cloud service credentials might grant full account access when specific resource permissions suffice.
Error Message Information Leakage: Detailed error messages exposing internal state or permission structures help attackers map system boundaries. Error responses should not reveal whether resources exist or which specific permission failed.
# Leaks information about system structure
def fetch_resource(id)
resource = Resource.find(id)
unless can_access?(resource)
raise Forbidden, "User #{current_user.id} lacks 'read' permission on resource #{id}"
end
resource
rescue ActiveRecord::RecordNotFound
raise NotFound, "Resource #{id} does not exist"
end
# Better: Uniform responses
def fetch_resource(id)
resource = Resource.find(id)
unless can_access?(resource)
raise NotFound # Same response as missing resource
end
resource
rescue ActiveRecord::RecordNotFound
raise NotFound
end
File Permission Defaults: Creating files without explicitly setting permissions relies on system umask, which may be permissive. Applications should set restrictive permissions on sensitive files.
Shared Credentials: Multiple services sharing credentials violates least privilege by preventing per-service access restriction. Compromising one service exposes credentials valid for all services.
Insufficient Privilege Separation in Testing: Test environments often run with elevated privileges to simplify test setup, but this hides authorization bugs that surface in production.
# Test that passes incorrectly
RSpec.describe UserController do
before { allow_any_instance_of(UserController).to receive(:current_user).and_return(admin_user) }
it 'updates user' do
patch :update, params: { id: user.id, name: 'New Name' }
expect(response).to be_successful
end
end
# Test that verifies authorization
RSpec.describe UserController do
context 'as regular user' do
before { sign_in(regular_user) }
it 'cannot update other users' do
patch :update, params: { id: other_user.id, name: 'New Name' }
expect(response).to be_forbidden
end
end
context 'as admin' do
before { sign_in(admin_user) }
it 'can update any user' do
patch :update, params: { id: user.id, name: 'New Name' }
expect(response).to be_successful
end
end
end
Implicit Trust in Downstream Services: Applications trusting data or requests from downstream services without validation assume those services correctly enforce privileges. Compromise of a downstream service bypasses application security.
Practical Examples
Real-world scenarios demonstrate how to apply least privilege principles across different system components and architectures.
Multi-Tenant Application with Data Isolation: A SaaS application must ensure each tenant accesses only their data. Least privilege restricts database connections and queries to current tenant scope.
class MultiTenantDatabase
def initialize(tenant_id)
@tenant_id = tenant_id
@connection = establish_connection(tenant_id)
end
def query(sql, params = [])
# All queries automatically scoped to tenant
scoped_sql = add_tenant_scope(sql)
@connection.exec_params(scoped_sql, [@tenant_id] + params)
end
private
def establish_connection(tenant_id)
# Database user has RLS policy enforcing tenant_id
PG.connect(
dbname: 'multi_tenant',
user: "tenant_#{tenant_id}",
password: fetch_tenant_password(tenant_id)
)
end
def add_tenant_scope(sql)
# Parse and inject WHERE tenant_id = $1 clause
# Simplified example - production would use query parser
sql.gsub(/FROM (\w+)/, 'FROM \1 WHERE tenant_id = $1')
end
end
class TenantController
before_action :set_tenant
def index
@records = @tenant_db.query('SELECT * FROM records')
end
private
def set_tenant
@tenant_db = MultiTenantDatabase.new(current_tenant.id)
end
end
Microservice Communication with Service Accounts: Microservices communicate using service-specific credentials rather than shared secrets. Each service has credentials granting access only to required endpoints.
class ServiceAuthenticator
def initialize(service_name)
@service_name = service_name
@credentials = load_service_credentials(service_name)
end
def call_service(target_service, endpoint, data)
token = generate_service_token(target_service)
HTTP.auth("Bearer #{token}")
.post("https://#{target_service}/#{endpoint}", json: data)
end
private
def generate_service_token(target_service)
# JWT with specific claims for service-to-service auth
payload = {
iss: @service_name,
aud: target_service,
exp: Time.now.to_i + 300, # 5 minute expiry
scopes: allowed_scopes(@service_name, target_service)
}
JWT.encode(payload, @credentials[:private_key], 'RS256')
end
def allowed_scopes(source, target)
# Define allowed operations between services
SERVICE_PERMISSIONS.dig(source, target) || []
end
end
SERVICE_PERMISSIONS = {
'user_service' => {
'notification_service' => ['send_notification'],
'billing_service' => ['check_subscription']
},
'order_service' => {
'inventory_service' => ['reserve_items', 'release_items'],
'billing_service' => ['charge_customer']
}
}
Background Job Processing with Restricted Access: Background workers should not have access to production credentials or sensitive operations beyond their specific tasks.
class BackgroundWorker
def perform(job_data)
# Worker uses dedicated credentials with limited scope
@connection = PG.connect(
user: 'background_worker',
password: ENV['WORKER_PASSWORD']
)
process_job(job_data)
ensure
@connection&.close
end
private
def process_job(job_data)
# Can only access worker-specific tables
@connection.exec_params(
'INSERT INTO job_results (data) VALUES ($1)',
[job_data]
)
end
end
# Database grants for background_worker:
# GRANT INSERT, UPDATE ON job_results TO background_worker;
# GRANT SELECT ON job_queue TO background_worker;
# No access to user data, financial data, or administrative tables
API Gateway with Route-Specific Permissions: An API gateway enforces different permission requirements based on route and HTTP method.
class APIGateway
ROUTE_PERMISSIONS = {
'GET /users' => ['users:read'],
'POST /users' => ['users:write'],
'DELETE /users/:id' => ['users:delete'],
'GET /admin/reports' => ['admin:read', 'reports:access'],
'POST /admin/settings' => ['admin:write']
}
def call(env)
request = Rack::Request.new(env)
route_pattern = extract_route_pattern(request)
required_permissions = ROUTE_PERMISSIONS[route_pattern]
unless has_permissions?(request, required_permissions)
return [403, {}, ['Forbidden']]
end
@app.call(env)
end
private
def has_permissions?(request, required_permissions)
token = extract_token(request)
user_permissions = decode_permissions(token)
required_permissions.all? { |perm| user_permissions.include?(perm) }
end
def extract_route_pattern(request)
# Match request path to route pattern
path = request.path_info
method = request.request_method
"#{method} #{normalize_path(path)}"
end
end
File Upload with Quarantine and Scanning: User-uploaded files are processed with minimal privileges and isolated from production systems until validated.
class FileUploadProcessor
QUARANTINE_DIR = '/var/quarantine'
SAFE_DIR = '/var/uploads'
def process_upload(uploaded_file)
# Step 1: Save to quarantine with restricted permissions
quarantine_path = save_to_quarantine(uploaded_file)
# Step 2: Scan in isolated environment
scan_result = scan_file(quarantine_path)
unless scan_result.safe?
File.delete(quarantine_path)
raise SecurityError, 'File failed security scan'
end
# Step 3: Move to safe directory
final_path = move_to_safe_storage(quarantine_path)
# Step 4: Process with limited privileges
process_with_restrictions(final_path)
end
private
def save_to_quarantine(file)
temp_path = File.join(QUARANTINE_DIR, SecureRandom.uuid)
File.open(temp_path, 'wb', 0600) do |f|
f.write(file.read)
end
# Quarantine directory owned by quarantine user, not app user
temp_path
end
def scan_file(path)
# Run scanner as separate user with read-only access
scanner = Process.spawn(
"sudo -u scanner /usr/bin/clamscan #{path}",
out: '/tmp/scan.log'
)
Process.wait(scanner)
ScanResult.new($?.exitstatus)
end
def move_to_safe_storage(source)
destination = File.join(SAFE_DIR, File.basename(source))
FileUtils.mv(source, destination)
File.chmod(0644, destination) # Read-only for application
destination
end
end
Reference
Permission Types and Scopes
| Permission Type | Description | Scope Level |
|---|---|---|
| Read | View data without modification | Resource-specific |
| Write | Create or modify data | Resource-specific |
| Delete | Remove data permanently | Resource-specific |
| Execute | Run operations or functions | Function-specific |
| Admin | Manage permissions and configuration | System-wide |
| Owner | Full control over specific resources | Resource-specific |
Access Control Models
| Model | Description | Use Case |
|---|---|---|
| DAC | Discretionary Access Control - owners control access | File systems, documents |
| MAC | Mandatory Access Control - system enforces policy | Military, classified systems |
| RBAC | Role-Based Access Control - access via roles | Enterprise applications |
| ABAC | Attribute-Based Access Control - context-aware | Dynamic cloud environments |
Ruby Security Methods
| Method | Purpose | Example Context |
|---|---|---|
| File.open(path, mode) | Restrict file operations by mode | r for read-only, w for write-only |
| Process.uid | Get current user ID | Verify non-root execution |
| Process::Sys.setuid | Drop privileges | Startup privilege reduction |
| File.chmod(mode, path) | Set file permissions | Restrict access to sensitive files |
| FileUtils.rm(path, secure: true) | Secure deletion | Prevent path traversal attacks |
Common Database Permission Grants
| Grant Type | Operations Allowed | Typical Use |
|---|---|---|
| SELECT | Read operations only | Reporting, analytics |
| INSERT | Create new records | Data collection services |
| UPDATE | Modify existing records | User preference updates |
| DELETE | Remove records | Data retention cleanup |
| EXECUTE | Run stored procedures | Controlled business logic |
| CONNECT | Establish connection | Basic database access |
Privilege Escalation Patterns
| Pattern | Mechanism | Duration |
|---|---|---|
| Sudo-style | Re-authentication required | Single operation |
| Time-limited token | Temporary elevated token | Minutes to hours |
| Approval workflow | Manager authorization | Operation-specific |
| Break-glass | Emergency access with audit | Immediate, logged |
Security Principle Hierarchy
| Principle | Relationship to Least Privilege | Priority |
|---|---|---|
| Defense in Depth | Least privilege creates multiple barriers | High |
| Fail-Safe Defaults | Denies access unless explicitly granted | High |
| Separation of Duties | Different privileges for different functions | Medium |
| Complete Mediation | Every access requires privilege check | High |
| Least Common Mechanism | Minimizes shared high-privilege components | Medium |
Ruby Permission Check Patterns
# Before action pattern
before_action :require_permission
def require_permission
unless current_user.has_permission?(required_permission)
raise Forbidden
end
end
# Policy object pattern
def authorize_action
policy = ResourcePolicy.new(current_user, resource)
raise Forbidden unless policy.allowed?(action)
end
# Capability pattern
def with_capability(capability)
raise SecurityError unless capability.valid?
yield capability
end
Environment Variable Access Patterns
| Pattern | Security Level | Use Case |
|---|---|---|
| Direct ENV access | Low - unrestricted | Non-sensitive configuration |
| Restricted wrapper | Medium - allowlist | Service-specific config |
| Encrypted secrets | High - encrypted at rest | Credentials, tokens |
| Vault integration | Highest - dynamic secrets | Production credentials |
File Permission Modes
| Octal | Symbolic | Description | Use Case |
|---|---|---|---|
| 0600 | rw------- | Owner read/write only | Private keys, secrets |
| 0644 | rw-r--r-- | Owner write, others read | Configuration files |
| 0400 | r-------- | Owner read only | Read-only credentials |
| 0700 | rwx------ | Owner full access | Private executables |
| 0755 | rwxr-xr-x | Owner write, others execute | Public executables |