🔒 Security Overview

Security is not an add-on.
It is the product.

Bastionary was designed security-first. Every authentication primitive, token type, and audit event is engineered to hold up under adversarial conditions — not bolted on after launch.

Token security that's actually modern

JWT signing with asymmetric keys, short-lived tokens, and RFC-compliant sender-constrained credentials by default.

🔐
Asymmetric JWT Signing
  • RS256, ES256, EdDSA supported (JWKS endpoint auto-published)
  • Key rotation without downtime — multiple public keys in JWKS
  • HS256 fallback for development only
  • Configurable via BASTION_JWT_ALGORITHM env var
♻️
Token Lifecycle
  • Access tokens: 15-minute TTL
  • Refresh token rotation: every exchange issues a new token
  • Refresh tokens tied to session record — revocable per device
  • Server-side session table — logout is immediate and authoritative
🔗
Sender-Constrained Tokens
  • DPoP (RFC 9449) — tokens bound to client keypair
  • PAR (RFC 9126) — pushed authorization requests for PKCE flows
  • Prevents token replay even if intercepted
  • Full PKCE support for all OAuth2 flows

Defense-in-depth at every login

Risk scoring, impossible travel detection, and breach checking happen on every authentication attempt — not just suspicious ones.

⚠️
Adaptive Risk Engine
  • Risk score computed on every login (0–100)
  • Score ≥ 70 without MFA → login blocked (RISK_TOO_HIGH)
  • Score ≥ 40 without MFA → step-up MFA required
  • Factors: new device, new IP, new country, odd hours, impossible travel
🛡️
Credential Hardening
  • bcrypt hashing (cost factor 12) — no plaintext, no SHA-1
  • HaveIBeenPwned breach check on every login and password change
  • Configurable password policy (length, complexity, history)
  • Account lockout after N failed attempts — configurable
🚦
Rate Limiting & Abuse
  • AUTH.LOGIN: 10 req/min per IP (spike-guarded)
  • AUTH.FORGOT_PASSWORD: 3 req/hour per email
  • General execute: 600 req/min per token
  • Configurable per-endpoint via admin dashboard

Your data stays yours, encrypted at rest

🔒
Encryption
  • PII field encryption via Fernet (AES-128-CBC + HMAC-SHA256)
  • Searchable encrypted fields via HMAC-SHA256 hash index
  • TLS everywhere: Caddy auto-HTTPS with Let's Encrypt
  • Action secrets for webhooks: Fernet-encrypted at rest
📋
Audit Trail Integrity
  • Append-only audit log — no deletes, no edits
  • SHA-256 hash chain: each entry hashes the previous
  • Tamper-evident: chain break detectable in O(n)
  • Config change auditing: before/after diff on every setting change
📄
Compliance Controls
  • GDPR consent management — versioned, immutable consent records
  • Data retention policies with automated purging
  • Right to erasure (GDPR Art. 17) — cryptographic erasure path
  • SOC 2 Type II controls implemented and observable

Self-hosted means you control the attack surface

🏠
Self-Hosted by Default
  • No data ever leaves your infrastructure
  • No telemetry, no phoning home, no vendor lock-in
  • Full source available — audit everything
  • Air-gapped deployment supported (offline license validation)
⚙️
Hardened Stack
  • PostgreSQL 16 with async I/O — no sqlite in production
  • Gunicorn + uvicorn workers — crash-isolated
  • Caddy reverse proxy with automatic HSTS, CSP headers
  • Configurable CSP, CORS, and security headers per endpoint
🔑
Secrets Management
  • JWT keys generated on first boot, stored on disk beside DB
  • All secrets configurable via environment variables
  • API key hashing: stored as SHA-256, never as plaintext
  • Webhook secrets stored encrypted (Fernet)

Security questions? We respond to every report.

Found a vulnerability? Use our contact form — select "Security / Vulnerability" in the subject. We acknowledge within 48 hours.