| title | description | updated |
|---|---|---|
Security |
Security considerations and audit notes for core-developer |
2026-01-29 |
The core-developer package provides powerful administrative capabilities that require careful security controls. This document outlines the security model, known risks, and mitigation strategies.
- Application logs - May contain tokens, passwords, PII in error messages
- Database access - Read-only query execution against production data
- SSH keys - Encrypted private keys for server connections
- Cache data - Application cache, session data, config cache
- Route information - Full application route structure
- Unauthorized users - Non-Hades users attempting to access dev tools
- Compromised Hades account - Attacker with valid Hades credentials
- SSRF/Injection - Attacker manipulating dev tools to access internal resources
- Data exfiltration - Extracting sensitive data via dev tools
All developer tools require "Hades" access, verified via the isHades() method on the User model. This is enforced at multiple layers:
| Layer | Implementation | File |
|---|---|---|
| Middleware | RequireHades::handle() |
src/Middleware/RequireHades.php |
| Component | checkHadesAccess() in mount() |
All Livewire components |
| API | Controller authorize() calls |
src/Controllers/DevController.php |
| Menu | admin flag filtering |
src/Boot.php |
The authorization is intentionally redundant:
- API routes use
RequireHadesmiddleware - Livewire components check in
mount() - Some controller methods call
$this->authorize()
This ensures access is blocked even if one layer fails.
Tests currently pass without setting Hades tier on the test user. This suggests authorization may not be properly enforced in the test environment. See TODO.md for remediation.
The LogReaderService automatically redacts sensitive patterns before displaying logs:
| Pattern | Replacement |
|---|---|
| Stripe API keys | [STRIPE_KEY_REDACTED] |
| GitHub tokens | [GITHUB_TOKEN_REDACTED] |
| Bearer tokens | Bearer [TOKEN_REDACTED] |
| API keys/secrets | [KEY_REDACTED] / [REDACTED] |
| AWS credentials | [AWS_KEY_REDACTED] / [AWS_SECRET_REDACTED] |
| Database URLs | Connection strings with [USER]:[PASS] |
| Email addresses | Partial: jo***@example.com |
| IP addresses | Partial: 192.168.xxx.xxx |
| Credit card numbers | [CARD_REDACTED] |
| JWT tokens | [JWT_REDACTED] |
| Private keys | [PRIVATE_KEY_REDACTED] |
Limitation: Patterns are regex-based and may not catch all sensitive data. Custom application secrets with non-standard formats will not be redacted.
Server private keys are:
- Encrypted at rest using Laravel's
encryptedcast - Hidden from serialization (
$hiddenarray) - Never exposed in API responses or views
- Stored in
textcolumn (supports long keys)
The database query component restricts access to read-only operations:
protected const ALLOWED_STATEMENTS = ['SELECT', 'SHOW', 'DESCRIBE', 'EXPLAIN'];Known Risk: The current implementation only checks the first word, which does not prevent:
- Stacked queries:
SELECT 1; DROP TABLE users - Subqueries with side effects (MySQL stored procedures)
Mitigation: Use a proper SQL parser or prevent semicolons entirely.
The /hub/api/dev/session endpoint exposes:
- Session ID
- User IP address
- User agent (truncated to 100 chars)
- Request method and URL
This is intentional for debugging but could be abused for session hijacking if credentials are compromised.
All API endpoints have rate limits to prevent abuse:
| Endpoint | Limit | Rationale |
|---|---|---|
| Cache clear | 10/min | Prevent DoS via rapid cache invalidation |
| Log reading | 30/min | Limit log scraping |
| Route listing | 30/min | Prevent enumeration attacks |
| Session info | 60/min | Higher limit for debugging workflows |
Rate limits are per-user (authenticated) or per-IP (unauthenticated).
The testConnection() method in Servers.php creates a temporary key file:
$tempKeyPath = sys_get_temp_dir().'/ssh_test_'.uniqid();
file_put_contents($tempKeyPath, $server->getDecryptedPrivateKey());
chmod($tempKeyPath, 0600);Risk: Predictable filename pattern and race condition window between write and use.
Recommendation: Use tempnam() for unique filename, write with restrictive umask.
StrictHostKeyChecking=nois used for convenience but prevents MITM detectionBatchMode=yesprevents interactive promptsConnectTimeout=10limits hanging connections
The RemoteServerManager::connect() method validates workspace ownership before connecting:
if (! $server->belongsToCurrentWorkspace()) {
throw new SshConnectionException('Unauthorised access to server.', $server->name);
}This prevents cross-tenant server access.
Route testing is only available in local and testing environments:
public function isTestingAllowed(): bool
{
return App::environment(['local', 'testing']);
}This prevents accidental data modification in production.
Routes using DELETE, PUT, PATCH, POST methods are marked as destructive and show warnings in the UI.
Test requests bypass CSRF as they are internal requests. The X-Requested-With: XMLHttpRequest header is set by default.
The SetHadesCookie listener sets a cookie on login:
| Attribute | Value | Purpose |
|---|---|---|
| Value | Encrypted token | Validates Hades status |
| Duration | 1 year | Long-lived for convenience |
| HttpOnly | true | Prevents XSS access |
| Secure | true (production) | HTTPS only in production |
| SameSite | lax | CSRF protection |
ApplyIconSettings middleware reads icon-style and icon-size cookies set by JavaScript. These are stored in session for Blade component access.
Risk: Cookie values are user-controlled. Ensure they are properly escaped in views.
| Action | What's Logged |
|---|---|
| Log clear | user_id, email, previous_size_bytes, IP |
| Database query | user_id, email, query, row_count, execution_time, IP |
| Blocked query | user_id, email, query (attempted), IP |
| Route test | user_id, route, method, IP |
| Server failure | Server ID, failure reason (via activity log) |
Server model uses Spatie ActivityLog for tracking changes:
- Logged fields: name, ip, port, user, status
- Only dirty attributes logged
- Empty logs suppressed
- Sensitive headers hidden:
cookie,x-csrf-token,x-xsrf-token - Sensitive parameters hidden:
_token - Gate restricts to Hades users (production) or all users (local)
- Gate restricts to Hades users
- Notifications configured via config (not hardcoded emails)
When adding new developer tools:
- Enforce Hades authorization in middleware AND component
- Add rate limiting for API endpoints
- Redact sensitive data in output
- Audit destructive operations
- Restrict environment (local/testing) for dangerous features
- Validate and sanitize all user input
- Use prepared statements for database queries
- Clean up temporary files/resources
- Document security considerations
- Revoke the user's Hades access
- Rotate
HADES_TOKENenvironment variable - Review audit logs for suspicious activity
- Check server access logs for SSH activity
- Consider rotating SSH keys for connected servers
- Delete the server record immediately
- Regenerate SSH key on the actual server
- Review server logs for unauthorized access
- Update the server record with new key
- Separate Hades token per environment - Don't use same token across staging/production
- Regular audit log review - Monitor for unusual access patterns
- Limit Hades users - Only grant to essential personnel
- Use hardware keys - For servers, prefer hardware security modules
- Network segmentation - Restrict admin panel to internal networks
- Two-factor authentication - Require 2FA for Hades-tier accounts
- Session timeout - Consider shorter session duration for Hades users