How to Secure a Kaneo Installation: A Complete Hardening Guide
Secure a Kaneo installation by generating a strong 32+ character AUTH_SECRET, restricting CORS_ORIGINS to your domain, deploying behind a TLS-terminating reverse proxy, running containers as non-root, and subscribing to the audit event stream.
Kaneo is a self-hosted project-management platform built as a pnpm monorepo with clear separation between the API backend (apps/api) and web frontend (apps/web). This guide explains how to secure each layer of your deployment, with direct references to the source code in the usekaneo/kaneo repository.
Environment and Secrets Management
All sensitive configuration lives in a single .env file at the repository root. The project provides a template at .env.sample listing required variables:
AUTH_SECRET— JWT signing secret (minimum 32 characters)DATABASE_URL— PostgreSQL connection stringKANEO_API_URL— Public-facing API endpointOAUTH_*credentials — For optional OAuth providers
Never commit .env to version control. The file is already included in .gitignore.
Generate a cryptographically secure secret:
openssl rand -base64 48
Then set it in your .env:
AUTH_SECRET=your-generated-secret-here
Authentication Hardening
Kaneo uses Better Auth for JWT-based authentication. The API validates secret strength on startup in [apps/api/src/auth.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts):
// apps/api/src/auth.ts
if (process.env.AUTH_SECRET && process.env.AUTH_SECRET.length < 32) {
throw new Error(
"AUTH_SECRET is less than 32 characters, please generate a new one."
);
}
export const auth = createBetterAuth({
secret: process.env.AUTH_SECRET || "",
});
The validation logic throws immediately if your secret is too weak, preventing the server from starting in an insecure state.
Authorization Patterns
Each authenticated request has its userId attached to the Hono context via c.get("userId"). Controllers enforce ownership checks before mutating resources. From [apps/api/src/project/controllers/delete-project.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/project/controllers/delete-project.ts):
export async function deleteProject(c: Context) {
const userId = c.get("userId");
const { id } = c.req.valid("param");
const project = await getProjectById(id);
if (project.ownerId !== userId) {
throw new HTTPException(403, { message: "Forbidden" });
}
await db.delete(projects).where(eq(projects.id, id));
return c.json({ success: true });
}
This "thin route – thick controller" pattern centralizes security logic rather than scattering it across middleware.
Database Security
The schema in [apps/api/src/database/schema.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/database/schema.ts) enforces several protective defaults:
- CUID2 primary keys — Prevent enumeration attacks compared to sequential IDs
- Cascade rules on foreign keys — Prevent orphaned records that could leak data
- Automatic timestamps — Support audit trails with
createdAtandupdatedAt
Review the schema file directly; its single-file structure makes full auditing straightforward.
Network and Transport Security
CORS Configuration
Control cross-origin access via the CORS_ORIGINS environment variable. The default development setting allows all origins, but production deployments must restrict this:
CORS_ORIGINS=https://kaneo.example.com
TLS Termination
Kaneo does not enforce HTTPS internally. Always deploy behind a reverse proxy (Nginx, Traefik, or Caddy) that:
- Terminates TLS with valid certificates (Let's Encrypt recommended)
- Forwards HTTP traffic to internal containers
- Sets appropriate
X-Forwarded-*headers
Port Exposure
The [compose.yml](https://github.com/usekaneo/kaneo/blob/main/compose.yml) exposes only necessary ports:
services:
api:
build:
context: .
dockerfile: Dockerfile.kaneo
ports:
- "1337:1337"
env_file: .env
web:
build: ./apps/web
ports:
- "5173:5173"
env_file: .env
Avoid exposing the database port externally. Keep PostgreSQL accessible only within the Docker network.
Container Hardening
The Dockerfile.kaneo implements security best practices:
# Multi-stage build with slim Node image
FROM node:20-slim AS runtime
# Run as non-root user
USER node
Additional hardening recommendations for your compose.yml:
services:
api:
read_only: true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
Audit Logging and Event Monitoring
All API endpoints publish events via publishEvent() defined in [apps/api/src/events/index.ts](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/events/index.ts). Subscribe to these events for security monitoring:
- Project creation/deletion
- User invitations and role changes
- Authentication events
Route events to your SIEM or a dedicated audit log store for forensic analysis.
CI/CD Pipeline Security
The [.github/workflows/ci.yml](https://github.com/usekaneo/kaneo/blob/main/.github/workflows/ci.yml) enforces quality gates before any merge to main:
- run: pnpm install
- run: pnpm lint
- run: pnpm test
- run: pnpm build
Secrets used in CI are stored in GitHub Secrets and never exposed to workflow logs. Run pnpm audit locally before pushing to catch vulnerable dependencies early.
Production Deployment Checklist
Use this checklist when deploying Kaneo to production:
- Generate strong secrets — 32+ character
AUTH_SECRETfromopenssl rand -base64 48 - Restrict CORS origins — Set exact domain match for
CORS_ORIGINS - Secure database access — PostgreSQL behind firewall, strong credentials, TLS connections
- Enable TLS termination — Reverse proxy with valid certificates
- Run containers as non-root — Verify
USER nodein Dockerfile builds - Enable read-only containers — Add
--read-onlywhere your environment permits - Subscribe to audit events — Implement
publishEvent()consumers for security monitoring - Keep dependencies updated — Run
pnpm auditand apply patches regularly
Summary
- Environment hardening: Keep secrets in
.env, never committed, with 32+ characterAUTH_SECRET - Authentication: Better Auth with startup validation in
apps/api/src/auth.ts - Authorization: Controllers check
c.get("userId")before resource access - Database: CUID2 keys, cascade rules, and consolidated schema in
apps/api/src/database/schema.ts - Network: Restrict
CORS_ORIGINS, deploy behind TLS-terminating reverse proxy - Containers: Non-root execution in
Dockerfile.kaneo, minimal port exposure incompose.yml - Monitoring: Subscribe to
publishEvent()for audit trails - CI/CD: Automated testing and linting in
.github/workflows/ci.yml
Frequently Asked Questions
What is the minimum length for the AUTH_SECRET in Kaneo?
Kaneo requires AUTH_SECRET to be at least 32 characters. The API validates this on startup in apps/api/src/auth.ts and throws an error if the secret is too short, preventing the server from running with weak authentication.
Does Kaneo enforce HTTPS automatically?
No. Kaneo handles HTTP traffic internally and expects TLS termination at a reverse proxy. Configure Nginx, Traefik, or Caddy with valid certificates in front of your Kaneo containers. The CORS_ORIGINS environment variable should then use the https:// scheme.
How do I audit user actions in Kaneo?
All mutations publish events through publishEvent() in apps/api/src/events/index.ts. Implement a subscriber that persists these events to your logging infrastructure. Events cover authentication, project changes, and permission modifications.
Can I run Kaneo containers as root?
The official Dockerfile.kaneo explicitly sets USER node to run as a non-privileged user. Running as root increases container escape risk. For additional hardening, add read_only: true and cap_drop: [ALL] in your Docker Compose configuration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →