Security
This guide covers the security features built into Svelar and best practices for hardening your application.
Environment & Secrets
APP_KEY
The APP_KEY is used to encrypt sessions and sign tokens. It must be a strong, random string.
npx svelar key:generate
This generates a 64-character hex key and writes it to .env. Never commit your .env file to version control.
Accessing Secrets in SvelteKit
SvelteKit uses Vite, which does not populate process.env from .env files. Always use SvelteKit's built-in env module:
// hooks.server.ts
import { env } from '$env/dynamic/private';
export const { handle } = createSvelarApp({
auth,
secret: env.APP_KEY,
});
Never use process.env for secrets in SvelteKit server code. The CLI (npx svelar) loads .env automatically, but the Vite dev server does not.
Secret Rotation
When rotating APP_KEY:
- Set the new key in
.env - Restart the application
- All existing sessions will be invalidated (users must re-login)
Authentication
Session Security
Svelar sessions use DatabaseSessionStore by default (persists across restarts). Session cookies are configured with:
httpOnly: true— not accessible via JavaScriptsecure: truein production — only sent over HTTPSsameSite: 'lax'— CSRF protection for cross-origin requests- Configurable lifetime (default: 2 hours)
Password Hashing
Svelar supports three hashing drivers:
| Driver | When to use |
|---|---|
scrypt |
Default, built into Node.js, no native deps |
bcrypt |
Industry standard, requires bcrypt package |
argon2 |
Strongest, requires argon2 package |
Hash.configure({ driver: 'scrypt' });
All drivers use safe defaults (salt generation, appropriate work factors). Never store passwords in plain text.
JWT & Refresh Tokens
- Access tokens are short-lived (default: 15 minutes)
- Refresh tokens are long-lived and stored in the database
- Refresh tokens are single-use (rotated on each refresh)
- Revoked tokens are rejected immediately
API Token Security
- API keys are hashed before storage (only the prefix is stored in plain text for lookup)
- Tokens can be scoped to specific permissions
- Tokens can have expiration dates
- Use
Authorization: Bearer <token>header
Middleware Pipeline
The default middleware stack provides layered security:
1. Origin Validation (OriginMiddleware)
Rejects requests from unauthorized origins. Prevents DNS rebinding attacks.
2. Rate Limiting (RateLimitMiddleware)
Prevents brute-force and abuse:
new RateLimitMiddleware({
maxRequests: 100,
windowMs: 60_000, // 100 requests per minute
})
3. CSRF Protection (CsrfMiddleware)
Validates CSRF tokens on state-changing requests (POST, PUT, DELETE). Automatically excluded for:
- API routes using Bearer token auth
- Internal API paths (
/api/internal/)
4. Request Signatures (SignatureMiddleware)
For API-to-API communication, verify request integrity with HMAC signatures:
new SignatureMiddleware({
secret: env.INTERNAL_SECRET,
paths: ['/api/internal/'],
})
CORS
Configure CORS headers in your middleware:
new CorsMiddleware({
origins: ['https://yourdomain.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
})
Never use origins: ['*'] in production with credentials: true.
Permissions & Authorization
Svelar includes a Spatie-inspired permission system:
- Roles group permissions (e.g.,
admin,editor) - Permissions define granular access (e.g.,
manage-users,edit-posts) - Roles can be assigned to users via pivot tables
- Direct permissions can be granted to individual users
- Gate checks enforce authorization at the controller level
// Check in controllers
if (!await user.hasPermission('manage-users')) {
return json({ error: 'Forbidden' }, { status: 403 });
}
Docker & Infrastructure Security
Port Exposure
Only the application port should be exposed to the host. All backing services (database, Redis, Soketi, Gotenberg) communicate over the internal Docker network.
| Service | Exposed to host? | Notes |
|---|---|---|
| app | Yes (port 3000) | The only public-facing service |
| postgres/mysql | No | Internal only |
| redis | No | Internal only, password required |
| soketi | No | Uncomment port if browser clients connect directly |
| gotenberg | No | Internal only |
| rustfs S3 API | No | Internal only (app connects via Docker network) |
| rustfs console | Yes (port 9001) | Admin dashboard — protect with firewall |
| meilisearch | No | Internal only (opt-in with --meilisearch) |
Redis Authentication
Redis is configured with password authentication by default:
redis:
command: redis-server --requirepass ${REDIS_PASSWORD:-svelarsecret}
Change REDIS_PASSWORD in .env before deploying to production.
Database Credentials
Default credentials in docker-compose.yml are for development only. Before deploying:
- Set strong passwords in
.envforDB_PASSWORD,REDIS_PASSWORD,RUSTFS_ROOT_PASSWORD - Never use default passwords in production
- Consider using Docker secrets or a vault for sensitive values
RustFS Console
The RustFS admin console (port 9001) provides full access to object storage. In production:
- Restrict access via firewall rules (allow only admin IPs)
- Or remove the port mapping entirely and use the CLI for administration
- Change
RUSTFS_ROOT_USERandRUSTFS_ROOT_PASSWORDfrom defaults
Input Validation
Always validate input at the boundary using Zod schemas:
import { z } from 'zod';
export const CreatePostSchema = z.object({
title: z.string().min(1).max(255),
content: z.string().min(1),
published: z.boolean().default(false),
});
Use FormRequest classes for automatic validation in controllers. Never trust client-side validation alone.
SQL Injection Prevention
The Svelar ORM uses parameterized queries by default. All query builder methods automatically escape values:
// Safe — values are parameterized
const user = await User.query().where('email', email).first();
// If using raw queries, always use parameters
const results = await Connection.raw('SELECT * FROM users WHERE email = ?', [email]);
Never interpolate user input into raw SQL strings.
Production Checklist
Before deploying to production:
- Generate a strong
APP_KEYwithnpx svelar key:generate - Set unique
JWT_SECRETandINTERNAL_SECRET - Change all default passwords (
DB_PASSWORD,REDIS_PASSWORD,RUSTFS_ROOT_PASSWORD,MEILI_MASTER_KEY) - Set
NODE_ENV=production - Enable HTTPS (use a reverse proxy like Traefik or Nginx)
- Restrict CORS origins to your domain
- Configure rate limiting appropriate for your traffic
- Remove or firewall the RustFS console port (9001)
- Review exposed Docker ports — only app:3000 should be public
- Set up log rotation to prevent disk exhaustion
- Enable database backups
- Never commit
.envfiles to version control
Reporting Vulnerabilities
If you discover a security vulnerability in Svelar, please report it responsibly by opening a private issue on GitHub. Do not disclose security issues publicly until a fix is available.