Skip to content

Security: aolus-software/clean-elysia-prisma

Security

.github/SECURITY.md

Security Policy

This file is the vulnerability disclosure policy for the repository. It is not a description of how the API's security works — that lives in docs/SECURITY.md, which documents the auth flow, password rules, token lifecycle, RBAC model, rate limits, and security headers. Read that first if your question is "how is this supposed to behave?"; come back here if the answer is "not like that, and it is exploitable."

Supported versions

This is a starter template rather than a versioned library. Security fixes land on main, and projects built from the template are expected to pull them forward themselves. Only the current main branch is supported.

Reporting a vulnerability

Do not open a public issue for a security vulnerability.

Report it privately through GitHub Security Advisories:

https://github.com/aolus-software/clean-elysia-prisma/security/advisories/new

That creates a private thread visible only to the maintainers. If you cannot use GitHub Advisories, open a regular issue that says only "security report, please provide a private contact" with no technical detail, and a maintainer will follow up.

Please include, as far as you can determine it:

  • the affected component (plugin, guard, route, service, repository, config) and file:line if you have it,
  • the conditions required to reach it — authenticated or not, which role or permission, which data,
  • a proof-of-concept request or sequence, including the response envelope you got back,
  • the impact you believe it has,
  • whether it reproduces with APP_CLUSTER_MODE=false, true, or both.

You should get an acknowledgement within a few days. Please give the maintainers a reasonable window to ship a fix before disclosing publicly.

Scope

In scope — anything that ships in this repository:

  • authentication and token handling: AuthPlugin (src/libs/plugins/auth.plugin.ts), JWT issuance via @elysiajs/jwt and JWT_CONFIG, login, register, email verification, password reset,
  • the RBAC layer: RoleGuard and PermissionGuard in @guards, and whether a route that should sit below .use(AuthPlugin) actually does,
  • repository select leaks — a query that returns a password hash, raw token, or another field the response schema was supposed to exclude (see .claude/rules/repositories.md rule 4 and .claude/rules/validation.md rule 12),
  • sort/filter allowlist bypasses in list endpoints that let user input reach orderBy or where keys,
  • secret handling: bcrypt password hashing in @utils, APP_KEY, JWT_SECRET, and anything leaking into logs, error envelopes, or OpenAPI examples,
  • SecurityPlugin (src/libs/plugins/security.plugin.ts): the CORS config, elysia-rate-limit configuration, and the helmet CSP directives,
  • BodyLimitPlugin and any way to get an oversized or unparsed body past it,
  • the error contract — an error path that returns a stack trace, a third-party library message, or an internal identifier to the client (.claude/rules/errors-and-responses.md),
  • cluster-mode issues where a security control silently degrades because state is per-worker rather than shared (for example a rate-limit counter that is not backed by Redis),
  • dependency vulnerabilities that are reachable through code in this repo.

Out of scope:

  • vulnerabilities in a project built from this template that come from that project's own code,
  • anything that requires an .env the docs tell you not to use — for example leaving ALLOWED_HOST unset (which falls back to *) or running with the example secrets in production,
  • missing hardening that is documented as the deployer's responsibility (TLS termination, database and Redis network access, secret storage, running the container as a non-root user),
  • the ClickHouse analytics path when it is not configured — it is optional infrastructure,
  • reports generated by an automated scanner with no demonstrated exploit path.

Known hardening notes

These are documented rather than treated as vulnerabilities. Review every one of them before deploying anything built on this template.

  • Every secret in @config has a working default except JWT_SECRET. src/libs/config/env.config.ts gives APP_KEY, REDIS_PASSWORD, and the mail credentials envalid defaults, so a missing one does not fail startup — the app boots with a publicly known value. Set all of them explicitly and verify at deploy time; do not rely on startup validation to catch an omission.
  • JWT_SECRET is required and has no default. JWT_CONFIG (src/libs/config/jwt.config.ts) signs and verifies every token with it, so it is declared str() with no fallback and the process refuses to boot unconfigured. It used to default to "your-secret-key", and a second variable APP_JWT_SECRET sat next to it on AppConfig reading as if it were the signing key while being read by nothing — so an operator who set only that one shipped forgeable tokens. APP_JWT_SECRET was removed on 2026-08-21.
  • CORS defaults to *. CORSConfig falls back to origin: "*" when ALLOWED_HOST is empty. Set ALLOWED_HOST to your real front-end origins (comma-separated) before exposing the API. credentials is false in the shipped config — if you turn it on, a wildcard origin becomes unsafe rather than merely permissive.
  • The API reference is env-gated, not secret. DocsPlugin enables /docs whenever NODE_ENV !== "production". Staging deployments publish the full schema. Make sure NODE_ENV is actually production in production.
  • Rate limiting is global and generous. SecurityPlugin applies 100 requests per 60 seconds across all routes, from a single in-process limiter. Tighten it, and add a stricter limit on credential endpoints (login, register, forgot password), before going live.
  • The rate limiter is per-process. Under APP_CLUSTER_MODE=true each forked worker keeps its own counter, so the effective limit is multiplied by the worker count. Back the limiter with Redis if the limit is load-bearing for you.
  • Queue workers boot once per worker process. src/bull/index.ts registers BullMQ workers on import, and every cluster worker re-runs it. Confirm your jobs are idempotent (.claude/rules/queue.md rule 9) or gate worker startup on a single process.
  • The body limit is a fixed 100 KB header check. BodyLimitPlugin reads content-length; a request that omits the header is not caught here. Enforce a limit at your reverse proxy as well.
  • The destructive Make targets do exactly what they say. make db-push (prisma db push --force-reset), make db-drop (prisma migrate reset --force), and make fresh (both, then seed) wipe the database in whatever DATABASE_URL your .env points at. So does the prisma migrate dev step in the Husky pre-commit hook. Never point a local .env at a shared or production database.
  • Seed data is not production data. prisma/seed/ creates known accounts, roles, and permissions. Do not run make db-seed against a live environment.

There aren't any published security advisories