IaaS Platform handles authentication, multi-tenant organization data, and usage-based billing, so security is treated as a first-class concern. This document describes the current security model, known limitations, hardening guidance, and how to report a vulnerability.
| Mechanism | Details |
|---|---|
| Passwords | Hashed with bcrypt (golang.org/x/crypto/bcrypt), cost configurable via BCRYPT_COST (default 12, range 4–15; keep ≥ 10 in production). Passwords are never stored or returned as plaintext. |
| JWT | HS256 signed tokens (github.com/golang-jwt/jwt/v5) carrying user_id, email, role, plus issuer/issued-at/expiry claims. Lifetime defaults to 24h (JWT_EXPIRES_IN). Only HMAC signing methods are accepted during validation. |
| API keys | Generated as 32 random bytes (crypto/rand), prefixed iaas_, returned exactly once at signup. Only a SHA-256 hash is stored (users.api_key, unique index). Login never exposes the key. |
| Password reset | One-time tokens prefixed pwr_, stored as SHA-256 digests, TTL default 24h (PASSWORD_RESET_TTL_HOURS), single-use (used_at), and enumeration-safe (unknown emails still return success). |
| Production guard | In any environment other than development, the server refuses to boot if JWT_SECRET is empty, a known placeholder, or shorter than 32 bytes (internal/config/config.go). |
API access is authenticated via Authorization: Bearer <jwt> or X-API-Key: <key>; both are accepted by the auth middleware.
- Multi-tenancy is enforced at the service layer, not just the router: every org-scoped operation looks up the caller's membership (
FindMember) before acting. - Roles are
adminandmember. Admin-only operations (invites, join-request accept/revoke, suspension, member removal) reject non-admins with400. - Suspended members lose access automatically: the membership lookup excludes rows whose
suspended_untilis in the future. Suspensions are reversible and auto-expire. - Members cannot enumerate other members — non-admin membership listings return only the caller's own record (no names, emails, or roles of others).
- Self-destructive actions are rejected (
ErrSelfAction): a user cannot remove or suspend themselves.
- Baseline security headers are set on every response (
internal/middleware/security.go):X-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: no-referrer- Content-Security-Policy restricting scripts/styles/images/connect sources to self
Strict-Transport-Security(2-year, includeSubDomains, preload) is emitted when TLS is enabled in-process.
- The server can terminate TLS itself (
TLS_CERT_FILE/TLS_KEY_FILE), or sit behind a reverse proxy (recommended for production; seedocs/DEPLOYMENT.md).
- Rate limiting: a global in-memory token bucket (refill 1 token/sec, burst 10), keyed by client IP or
X-API-Keywhen present. Exceeded requests get429withRetry-After. - Timeouts: server-level
ReadTimeout/WriteTimeout/ReadHeaderTimeout/IdleTimeoutare set (15s/15s/10s/60s).
These are acknowledged and tracked; none block development, but production deployments should plan for them:
- No request-body size limit. Large payloads are bounded only by server timeouts. Terminate TLS and enforce body limits at a reverse proxy (or see the open issue to add
http.MaxBytesReader). - No per-account rate limiting or login lockout. Brute-force protection beyond bcrypt cost and the global limiter is future work.
- CORS allows all origins (
Access-Control-Allow-Origin: *). An allowlist configuration is planned. - In-memory rate limiter is per-process state — scale horizontally only with a shared store (e.g. Redis) or sticky sessions.
- No API-key rotation endpoint yet — keys are valid until changed in the database.
-
ENV=production -
JWT_SECRETset to a random value ≥ 32 bytes (the server refuses known placeholders in non-dev) -
BCRYPT_COST=12(or higher) - Terminate TLS in-process or at a reverse proxy with HSTS enabled
- Use a dedicated database user with least privilege; never the superuser
- SMTP credentials provided via environment or secret store, never committed
- Run the gitleaks secret scan (
go run github.com/gitleaks/gitleaks/v8@latest detect --config .gitleaks.toml) before every push; CI enforces it
- Passwords: bcrypt (one-way).
- API keys and reset tokens: SHA-256 digests at rest (one-way).
- JWT secrets, SMTP passwords, and database credentials: environment-only;
.envis gitignored and excluded from the Docker build context. - CI runs gitleaks and CodeQL on every push/PR to catch accidental secret commits and code-level vulnerabilities.
Please do not open a public issue for security problems.
- Privately report via the repository's Security → Report a vulnerability page (GitHub Security Advisories).
- If that is unavailable, open a private e-mail to the maintainer (address in the repository owner's profile).
Include a description of the issue, the affected version/branch, and a minimal reproduction. We will acknowledge within 5 business days and coordinate a fix before public disclosure.