···62626363On first run, you'll need to create an admin user:
64646565-1. Visit `https://your-indiko-domain.com/login?invite=bootstrap`
6565+1. Visit `https://your-indiko-domain.com/login`
66662. Register with a passkey
67673. This first user will automatically be an admin
6868···152152 add_header Referrer-Policy "strict-origin-when-cross-origin" always;
153153 add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
154154 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
155155-155155+156156 # Content Security Policy
157157 add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; script-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
158158
+186
SECURITY.md
···11+# Security Policy
22+33+## Reporting Security Vulnerabilities
44+55+If you discover a security vulnerability in Indiko, please report it privately:
66+77+- **Email:** security@dunkirk.sh
88+- **Do not** open public issues for security vulnerabilities
99+- You will receive a response within 48 hours
1010+1111+We appreciate responsible disclosure and will credit researchers who report vulnerabilities (unless you prefer to remain anonymous).
1212+1313+---
1414+1515+## Security Architecture
1616+1717+### Authentication Model
1818+1919+Indiko uses **WebAuthn/Passkeys** for passwordless authentication:
2020+2121+- **No passwords stored** - eliminates credential stuffing, dictionary attacks, and password database breaches
2222+- **Public key cryptography** - private keys never leave the user's device
2323+- **Phishing resistant** - cryptographic binding to domain prevents phishing
2424+- **Platform authenticators** - biometric authentication (Face ID, Touch ID, Windows Hello)
2525+2626+### OAuth 2.0 Implementation
2727+2828+**Authorization Code Flow with PKCE:**
2929+3030+- All authorization codes are **single-use** and expire in 60 seconds
3131+- **PKCE (S256)** required for public clients (auto-registered apps)
3232+- **Client secrets** (SHA-256 hashed) required for pre-registered confidential clients
3333+- Redirect URIs validated against registered allowlist
3434+3535+**Session Management:**
3636+3737+- Session tokens: cryptographically random UUIDs (128-bit entropy)
3838+- 24-hour session expiry with automatic cleanup (runs hourly)
3939+- HttpOnly cookies prevent XSS token theft
4040+- Secure flag enabled in production (HTTPS-only transmission)
4141+- SameSite=Lax provides CSRF protection
4242+4343+### Database Security
4444+4545+- **SQLite with WAL mode** for concurrent access
4646+- **Foreign keys enabled** for referential integrity
4747+- **Parameterized queries** throughout - no SQL injection vectors
4848+- **Credential data** stored as Buffers with proper encoding
4949+5050+---
5151+5252+## Known Security Considerations
5353+5454+### Rate Limiting ⚠️
5555+5656+Indiko does **not** currently implement rate limiting. This is acceptable for:
5757+5858+- Personal homelab deployments
5959+- Small team internal authentication
6060+- Deployments behind a reverse proxy with rate limiting (nginx, Caddy, Cloudflare)
6161+6262+If exposing Indiko to the public internet, implement rate limiting:
6363+6464+```typescript
6565+// Suggested limits per IP:
6666+- Challenge generation: 10 requests/minute
6767+- Login/registration: 5 attempts/minute
6868+- Token exchange: 20 requests/minute
6969+- Admin endpoints: 20 requests/minute
7070+```
7171+7272+### Bootstrap Mode
7373+7474+**First user registration** automatically becomes admin without invite code.
7575+7676+**Security implications:**
7777+7878+- Expose Indiko only after creating your admin account
7979+- Consider disabling bootstrap mode after first user (future feature)
8080+8181+### Invite Code Security
8282+8383+Invite codes use 16 bytes of cryptographic randomness (base64url encoded, ~128 bits entropy).
8484+8585+### Client Secret Management
8686+8787+Client secrets for pre-registered OAuth apps use 43-character nanoid (~256 bits entropy).
8888+8989+- Secrets shown **only once** at creation
9090+- Stored as SHA-256 hashes
9191+- No recovery mechanism - regenerate if lost
9292+- Rotate secrets if compromised or annually
9393+9494+---
9595+9696+## Production Deployment
9797+9898+Indiko is designed to run behind a reverse proxy in production. See the [README deployment section](README.md#production-deployment) for complete nginx/Caddy configurations with security headers.
9999+100100+**Environment Variables:**
101101+102102+- `ORIGIN` must be HTTPS URL in production (validated at startup)
103103+- `RP_ID` must match ORIGIN domain (validated at startup)
104104+- `NODE_ENV=production` enables Secure cookie flag
105105+106106+---
107107+108108+## Notes
109109+110110+### For Developers (OAuth Clients)
111111+112112+1. **Use PKCE:**
113113+114114+ - Always generate code_verifier (43+ character random string)
115115+ - Use S256 code_challenge_method
116116+ - Never reuse verifiers across authorization flows
117117+118118+2. **Protect client secrets:**
119119+120120+ - Store in environment variables or secret manager
121121+ - Never commit to version control
122122+ - Never expose in client-side code
123123+ - Rotate if compromised
124124+125125+3. **Validate tokens:**
126126+127127+ - Check `me` field matches expected user identity
128128+ - Verify `scope` includes required permissions
129129+ - Handle token errors gracefully
130130+ - Don't cache profile data indefinitely
131131+132132+4. **Respect user privacy:**
133133+ - Request minimum necessary scopes
134134+ - Provide clear app description and logo
135135+ - Implement logout/revoke functionality
136136+ - Don't share user data with third parties
137137+138138+---
139139+140140+## Compliance Notes
141141+142142+### GDPR (EU General Data Protection Regulation)
143143+144144+**Personal data stored:**
145145+146146+- Username, name, email (optional), photo URL (optional)
147147+- Passkey credential data (public keys only)
148148+- Session tokens (temporary)
149149+- OAuth authorization history
150150+151151+**User rights:**
152152+153153+- **Right to access:** Users can view profile at `/profile`
154154+- **Right to erasure:** Admins can delete user accounts (cascading delete)
155155+- **Right to data portability:** Export database manually (SQLite dump)
156156+- **Right to rectification:** Users can edit profile at `/profile`
157157+158158+**Data retention:**
159159+160160+- Active data retained while account exists
161161+- Expired sessions/challenges automatically cleaned hourly
162162+- Deleted accounts immediately remove all personal data (foreign key cascade)
163163+164164+### CCPA (California Consumer Privacy Act)
165165+166166+Indiko does not sell personal information. Users can request data deletion via admin.
167167+168168+### Cookie Policy
169169+170170+| Cookie Name | Purpose | Duration | Type |
171171+| ---------------- | ---------------------- | -------- | --------- |
172172+| `indiko_session` | Authentication session | 24 hours | Essential |
173173+174174+No tracking or analytics cookies are set by Indiko.
175175+176176+---
177177+178178+## Contact
179179+180180+- **Security Issues:** security@dunkirk.sh
181181+- **General Support:** https://tangled.org/@dunkirk.sh/indiko
182182+- **Maintainer:** Kieran Klukas (@taciturnaxolotl)
183183+184184+---
185185+186186+_Last Updated: December 18, 2025_