How oblien/openship Manages Secret Keys and Sensitive Information

oblien/openship stores all secret keys and sensitive configuration in environment variables, validates them with Zod schemas at startup, and injects critical secrets like BETTER_AUTH_SECRET into memory only once without ever logging or persisting them to disk.

The oblien/openship repository follows the twelve-factor app methodology for configuration management, ensuring that credentials, API keys, and encryption secrets remain external to the codebase. By leveraging environment variables validated through strict schemas, the application maintains security across development and production environments without hard-coding sensitive values.

Environment Variable Architecture

Openship adopts the classic twelve-factor approach of storing configuration in environment variables, keeping secrets out of source control. The repository provides an .env.example file at the root that serves as a comprehensive template, listing every variable the runtime expects—from database credentials and API keys to internal encryption secrets like BETTER_AUTH_SECRET.

Developers copy this template to a local .env file (which is explicitly excluded from Git via .gitignore) and populate it with secure values. This ensures that production secrets never appear in commit history while maintaining clear documentation of required configuration.

Database Credentials from Environment

Database connection parameters are resolved directly from process.env at module load time. In packages/db/src/client.ts, the client constructs connection strings using standard PostgreSQL environment variables:

const host = process.env.POSTGRES_HOST ?? process.env.PGHOST;
const user = process.env.POSTGRES_USER ?? process.env.PGUSER ?? "openship";
const password = process.env.POSTGRES_PASSWORD ?? process.env.PGPASSWORD;
const db = process.env.POSTGRES_DB ?? process.env.PGDATABASE ?? user;

This pattern ensures that database passwords and host information remain external to the codebase, allowing the same container image to run against different databases by changing environment variables alone.

Configuration Validation with Zod

To prevent runtime errors from missing or malformed secrets, openship validates all environment variables at application startup using a Zod schema defined in apps/api/src/config/env.ts. This creates a single source of truth for configuration types and default values.

The schema includes strict typing for sensitive fields while providing safe defaults for development:

import { z } from "zod";

const DEFAULT_BETTER_AUTH_SECRET = "change-me-in-production";

export const env = z.object({
  DATABASE_URL: z.string().default("postgres://postgres:postgres@localhost:5432/openship"),
  OBLIEN_CLIENT_ID: z.string().optional(),
  OBLIEN_CLIENT_SECRET: z.string().optional(),
  BETTER_AUTH_SECRET: z.string().default(DEFAULT_BETTER_AUTH_SECRET),
  // … additional variables …
}).parse(process.env);

When the application boots, z.parse() throws an error if required secrets are missing or invalid, failing fast before the server accepts traffic. The validated env object is then imported throughout the codebase, ensuring type-safe access to sensitive configuration.

Runtime Secret Injection

Critical internal secrets require special handling to prevent exposure in logs or stack traces. Openship implements an injection pattern for BETTER_AUTH_SECRET, the primary key used for encrypting cookies, backup credentials, and other persisted data.

The Injection Pattern

Located in packages/adapters/src/backup/common/credentials.ts, the injectBetterAuthSecret function resolves the secret from the environment exactly once at application boot:

/** Inject the API‑resolved BETTER_AUTH_SECRET. Call once at app boot. */
export const injectBetterAuthSecret = (injectedSecret?: string) => {
  const secret = injectedSecret ?? process.env.BETTER_AUTH_SECRET;
  if (!secret) {
    throw new Error("BETTER_AUTH_SECRET is not set — cannot decrypt backup destination credentials.");
  }
  return secret;
};

This design ensures that the encryption key resides only in memory after startup, never appearing in error messages, logs, or serialized state. The function throws immediately if the secret is unavailable, preventing the application from running in an insecure configuration.

Encryption at Rest

The BETTER_AUTH_SECRET derived from this injection pattern powers the application's encryption utilities. For example, in apps/api/src/lib/encryption.ts, the secret initializes AES-256-GCM ciphers for data protection:

import { env } from "./config/env";

export const encrypt = (plain: string) => {
  const cipher = crypto.createCipheriv("aes-256-gcm", hashKey(env.BETTER_AUTH_SECRET), iv);
  // … encryption logic …
};

Docker Deployment Security

When generating Docker Compose configurations, the CLI preserves existing secret values using the keepSecret helper in apps/cli/src/lib/compose.ts. This function ensures that sensitive variables are passed through to containers using Docker's environment variable syntax without exposing them in generated files:

function keepSecret(prev: Record<string, string>, key: string): string {
  return prev[key] ? `$${key}` : "";
}

// Usage in generated service definition:
`BETTER_AUTH_SECRET=${keepSecret(prev, "BETTER_AUTH_SECRET")}`

This approach allows the compose generator to update configuration files without overwriting production secrets, maintaining the security boundary between version-controlled templates and runtime configuration.

Summary

  • External Configuration: All secret keys and sensitive information live in environment variables, not source code, following the .env.example template.
  • Schema Validation: The Zod schema in apps/api/src/config/env.ts validates secrets at startup, throwing errors for missing required values.
  • Memory-Only Secrets: Critical keys like BETTER_AUTH_SECRET are injected once at boot and stored only in runtime memory, never logged or persisted.
  • Database Security: Connection credentials are read from standard PostgreSQL environment variables in packages/db/src/client.ts without hard-coded defaults.
  • Deployment Safety: The CLI's keepSecret helper ensures Docker Compose files reference secrets without exposing their values.

Frequently Asked Questions

Where are secret keys stored in oblien/openship?

Secret keys are stored exclusively in environment variables defined in a local .env file, which is never committed to Git. The repository only contains .env.example, a template documenting all required variables including BETTER_AUTH_SECRET, database passwords, and third-party API keys.

How does openship prevent missing secret errors in production?

The application validates all environment variables at startup using a Zod schema in apps/api/src/config/env.ts. If required secrets are missing or malformed, z.parse() throws an immediate error, preventing the server from starting in an insecure state.

What is the BETTER_AUTH_SECRET used for?

BETTER_AUTH_SECRET is the primary encryption key for the Better Auth library, used to encrypt cookies, backup destination credentials, and other sensitive persisted data. It is injected via injectBetterAuthSecret() at application boot and held only in memory.

Are secrets exposed in Docker Compose files?

No. The CLI generator in apps/cli/src/lib/compose.ts uses the keepSecret() helper to reference secrets using Docker's $VARIABLE syntax, ensuring that actual values are pulled from the host environment at runtime rather than written into generated YAML files.

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 →