# How to Secure a Kaneo Installation: A Complete Hardening Guide

> Learn how to secure your Kaneo installation with this comprehensive hardening guide. Discover essential steps like generating strong secrets, configuring CORS, and running non-root containers.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-02

---

**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](https://github.com/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`](https://github.com/usekaneo/kaneo/blob/main/.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:

```bash
openssl rand -base64 48

```

Then set it in your `.env`:

```bash
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)](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/auth.ts):

```typescript
// 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)](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/project/controllers/delete-project.ts):

```typescript
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)](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:

```bash
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)](https://github.com/usekaneo/kaneo/blob/main/compose.yml) exposes only necessary ports:

```yaml
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`](https://github.com/usekaneo/kaneo/blob/main/Dockerfile.kaneo) implements security best practices:

```dockerfile

# 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`](https://github.com/usekaneo/kaneo/blob/main/compose.yml):

```yaml
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)](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)](https://github.com/usekaneo/kaneo/blob/main/.github/workflows/ci.yml) enforces quality gates before any merge to `main`:

```yaml
- 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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/compose.yml)
- **Monitoring**: Subscribe to `publishEvent()` for audit trails
- **CI/CD**: Automated testing and linting in [`.github/workflows/ci.yml`](https://github.com/usekaneo/kaneo/blob/main/.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`](https://github.com/usekaneo/kaneo/blob/main/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`](https://github.com/usekaneo/kaneo/blob/main/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.