-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Mirrors
docs/security.mdin the repo.
Bot tokens (and Discord webhook URLs) are encrypted with AES-256-GCM
(lib/crypto.ts) using a 32-byte key from BOTFLEET_ENCRYPTION_KEY
(base64-encoded; generate with openssl rand -base64 32).
- Encryption/decryption only ever happens inside server-side code (route handlers, the seed script). Nothing client-side ever touches a plaintext or encrypted token.
- No API response includes
tokenEncryptedor a webhook'surlEncrypted- seelib/serialize.ts. There is no "masked" version returned either (e.g.sk-***1234); the field is simply never in the response shape. - Token rotation (
POST /api/admin/bots/:id/rotate-token) re-encrypts and overwrites in place; the audit log records that a rotation happened, not the token. - Nothing in this codebase calls
console.log/logs a decrypted secret. Grep fordecryptSecret(if you want to verify this yourself - every call site is insidelib/alerts/discord-webhook.ts(to actually POST to Discord) or a route that discards the value after use.
-
Admin routes (
/admin/*pages,/api/admin/*): guarded byrequireAdmin()(lib/require-admin.ts), which checkssession.user.roleisadminorowner. API routes always return a JSON401/403- never a redirect - so a fetch call never silently receives an HTML login page. -
Customer routes (
/api/customer/*): guarded byrequireCustomerSession()+loadOwnedBot()(lib/require-customer.ts).loadOwnedBot()returnsnull- and the route returns a generic 404 - both when a bot doesn't exist and when it exists but belongs to someone else, so bot IDs can't be enumerated to distinguish the two cases. -
First-run admin bootstrap:
BOTFLEET_ADMIN_DISCORD_IDSis a comma-separated allowlist of Discord user IDs. The first time one of those users signs in,auth.ts'ssessioncallback promotes them toowner. Additional admins can be promoted afterward at/admin/users- only anownercan change roles there, and the last remaining owner can never be demoted (guarded server-side inPATCH /api/admin/users/:id, not just hidden in the UI).
Set in next.config.ts for every response. script-src only allows
'unsafe-eval' in development (Next.js Fast Refresh needs it); production
builds get script-src 'self' with no eval.
GET /api/admin/security (rendered at /admin/security) computes a real
report from actual state - see lib/security-checks.ts. Every check
either reads an environment variable, queries the database, or reflects a
structural guarantee that's true by construction (e.g. "tokens encrypted
at rest" is true because there is no plaintext token column in the
schema). Nothing is a hardcoded score.
npm audit currently reports 2 moderate advisories, both in build/dev
tooling, not runtime app code:
-
@hono/node-server(via Prisma's localprisma devCLI, which this project doesn't use - we run against a real Postgres instance). -
postcss(bundled inside Next.js's build pipeline).
Both would require downgrading to a much older major version of Prisma or Next.js to "fix," which is a worse trade than the advisory itself. Revisit when upstream ships a patched release.
Please don't open a public issue for a security problem. Use GitHub's private vulnerability reporting (Security tab → "Report a vulnerability") on this repository.