System Design

Security Model

Authentication, authorization, path validation, encryption, and production hardening.

Security Model

Cognova is a single-user, self-hosted application. The security model focuses on protecting the instance from unauthorized network access and preventing filesystem escape.

Authentication

BetterAuth

Authentication is handled by BetterAuth with email/password credentials. The configuration lives in server/utils/auth.ts.

SettingValueDescription
ProvideremailAndPasswordNo OAuth providers configured by default
Session lifetime7 daysConfigurable via session.expiresIn
Session refreshEvery 24 hoursKeeps active sessions alive
Secure cookiesAuto-detectedEnabled when BETTER_AUTH_URL starts with https://
Trusted originsAuto-configuredBased on BETTER_AUTH_URL or dynamic when ACCESS_MODE=any

Session Flow

  1. User submits email/password to /api/auth/sign-in/email
  2. BetterAuth verifies the hashed password in the account table
  3. A session row is created with a unique token, client IP, and user agent
  4. The session token is set as an HTTP-only cookie
  5. Subsequent requests include the cookie, which BetterAuth validates against the session table
  6. Sessions expire after 7 days of inactivity

Default Admin Seeding

On first startup with an empty database, the seedIfEmpty() function creates a default user:

VariableDefault
ADMIN_EMAILadmin@example.com
ADMIN_PASSWORDchangeme123
ADMIN_NAMEAdmin

Change the default password immediately after first login. The seed logs a warning to the console as a reminder.

API Token Authentication

CLI tools and skills use a separate API token instead of cookie-based auth. The token is auto-generated on server startup and written to the .api-token file. You can override it by setting COGNOVA_API_TOKEN in your environment.

API routes check for either a valid session cookie or a matching Authorization: Bearer <token> header.

Authorization

Cognova uses a single-user model. There are no roles, permissions, or access control lists. If you are authenticated, you have full access to everything. The auth layer exists solely to prevent unauthorized network access to the instance.

Path Validation

All file operations are sandboxed to the vault directory via server/utils/path-validator.ts.

How It Works

The validatePath() function:

  1. Strips leading slashes from the requested path
  2. Resolves the path relative to VAULT_ROOT
  3. Computes the relative path from VAULT_ROOT to the resolved path
  4. Rejects the request with HTTP 403 if the relative path starts with .. or the resolved path does not start with VAULT_ROOT
Request: /api/fs/read?path=../../etc/passwd
Resolved: /etc/passwd
Relative to vault: ../../etc/passwd
Result: 403 "Path outside vault directory"

Vault Root Resolution

The vault root is determined once at server startup in this order:

  1. VAULT_PATH environment variable (recommended)
  2. /tmp/cognova-vault (if it exists)
  3. ~/Documents/vault (if it exists)
  4. Current working directory (fallback)

Terminal Sandboxing

The PTY manager spawns shell sessions rooted in the vault directory. The initial cwd is always set to getVaultRoot(). Note that the terminal itself does not restrict navigation -- a user with shell access can cd anywhere the process has permission to read. The terminal is an authenticated-only feature.

Encryption

Secret Storage

Secrets are encrypted at rest using AES-256-GCM. The implementation is in server/utils/crypto.ts.

ParameterValue
Algorithmaes-256-gcm
Key derivationscrypt with fixed salt
Key sourceBETTER_AUTH_SECRET environment variable
IV16 random bytes per secret (stored alongside)
Auth tagAppended to ciphertext, separated by :

The encryption flow:

  1. Derive a 32-byte key from BETTER_AUTH_SECRET using scryptSync
  2. Generate a random 16-byte IV
  3. Encrypt with aes-256-gcm, producing ciphertext + auth tag
  4. Store encrypted_value as ciphertext:authTag (hex) and iv as hex

The encryption key is derived from BETTER_AUTH_SECRET. If you rotate this secret, all stored secrets become unreadable. Back up the secret value before changing it.

Password Hashing

User passwords are hashed by BetterAuth using its built-in hashing (bcrypt). The hash is stored in the account.password column.

Database Security

Connection Security

The database connection auto-detects whether to use SSL based on the DATABASE_URL:

  • Neon (*.neon.tech) -- SSL required
  • Local (localhost, 127.0.0.1) -- SSL disabled
  • Other -- SSL disabled (configure manually if needed)

Graceful Degradation

If the database is unavailable, the server continues running with database features disabled. The requireDb() guard returns HTTP 503 for API routes that need the database, rather than crashing.

Advisory Locking

Migrations use PostgreSQL advisory locks (pg_try_advisory_lock) to prevent concurrent migration runs when multiple server instances start simultaneously. The lock is always released in a finally block.

Environment Variables

Critical secrets are loaded from environment variables, never hardcoded:

VariablePurposeRequired
BETTER_AUTH_SECRETSession signing, secret encryption key derivationYes
DATABASE_URLPostgreSQL connection stringNo (degrades gracefully)
VAULT_PATHFilesystem sandbox rootYes
BETTER_AUTH_URLAuth callback base URL, cookie domainRecommended
COGNOVA_API_TOKENFixed API token overrideNo (auto-generated)

The 00.env-validate plugin checks these on startup and logs warnings or errors for missing values.

See the Configuration page for the full environment variable reference.

Production Hardening Checklist

When deploying Cognova to a production environment:

  • Set BETTER_AUTH_URL to your HTTPS URL so cookies use the Secure flag
  • Generate a strong BETTER_AUTH_SECRET with openssl rand -base64 32
  • Change the default admin password immediately after first login
  • Use a strong, unique DATABASE_URL password
  • Run behind a reverse proxy (nginx, Caddy) that terminates TLS
  • Restrict network access to the Cognova port (firewall or VPN)
  • Set VAULT_PATH to a directory with appropriate filesystem permissions
  • Do not expose the PostgreSQL port to the public internet
  • Back up BETTER_AUTH_SECRET -- losing it makes stored secrets unrecoverable
  • Consider setting ACCESS_MODE to a specific origin rather than any