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 string
  • KANEO_API_URL — Public-facing API endpoint
  • OAUTH_* 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 createdAt and updatedAt

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:

  1. Terminates TLS with valid certificates (Let's Encrypt recommended)
  2. Forwards HTTP traffic to internal containers
  3. 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:

  1. Generate strong secrets — 32+ character AUTH_SECRET from openssl rand -base64 48
  2. Restrict CORS origins — Set exact domain match for CORS_ORIGINS
  3. Secure database access — PostgreSQL behind firewall, strong credentials, TLS connections
  4. Enable TLS termination — Reverse proxy with valid certificates
  5. Run containers as non-root — Verify USER node in Dockerfile builds
  6. Enable read-only containers — Add --read-only where your environment permits
  7. Subscribe to audit events — Implement publishEvent() consumers for security monitoring
  8. Keep dependencies updated — Run pnpm audit and apply patches regularly

Summary

  • Environment hardening: Keep secrets in .env, never committed, with 32+ character AUTH_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 in compose.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →