coleam00/Archon · critical
BETTER_AUTH_SECRET must be at least ${String(MIN_BETTER_AUTH
Error message
BETTER_AUTH_SECRET must be at least ${String(MIN_BETTER_AUTH_SECRET_LENGTH)} characters when web auth is enabled. Generate one with: openssl rand -base64 32 What it means
assertWebAuthAtBoot runs during startServer and refuses to boot with web authentication enabled but a BETTER_AUTH_SECRET shorter than the minimum length (MIN_BETTER_AUTH_SECRET_LENGTH). Better Auth signs session tokens with this secret; a weak/guessable key would let attackers forge sessions, so the server fails fast with remediation instructions instead of silently mounting auth on a weak key.
Source
Thrown at packages/server/src/auth/config.ts:39
/**
* Web auth is active only when a Postgres connection AND a signing secret are
* configured. SQLite installs (no DATABASE_URL) are always opted out.
*/
export function isWebAuthEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
return Boolean(env.DATABASE_URL && env.BETTER_AUTH_SECRET);
}
/**
* Fail fast at server boot: when web auth is enabled, the signing secret must be
* long enough to be a real secret. A short/placeholder secret would let an
* attacker forge sessions, so we throw with an actionable hint rather than
* silently mounting auth on a weak key.
*/
export function assertWebAuthAtBoot(env: NodeJS.ProcessEnv = process.env): void {
if (!isWebAuthEnabled(env)) return;
const secret = env.BETTER_AUTH_SECRET ?? '';
if (secret.length < MIN_BETTER_AUTH_SECRET_LENGTH) {
throw new Error(
`BETTER_AUTH_SECRET must be at least ${String(MIN_BETTER_AUTH_SECRET_LENGTH)} characters ` +
'when web auth is enabled. Generate one with: openssl rand -base64 32'
);
}
}
/**
* Parse the signup allowlist from `ARCHON_AUTH_ALLOWED_EMAILS` (comma-separated,
* lowercased, trimmed, blanks dropped). An empty/unset list does NOT mean open
* signup — see `getSignupMode` (empty defaults to `disabled` unless
* `ARCHON_AUTH_OPEN_SIGNUP=true`).
*/
export function parseAllowedEmails(env: NodeJS.ProcessEnv = process.env): string[] {
return (env.ARCHON_AUTH_ALLOWED_EMAILS ?? '')
.split(',')
.map(e => e.trim().toLowerCase())
.filter(Boolean);
}View on GitHub (pinned to 0773b97458)
Solutions
- Generate a strong secret with `openssl rand -base64 32` and set it as BETTER_AUTH_SECRET in the server environment/.env
- Verify the full secret was pasted (no truncation/whitespace) and its length meets the minimum
- If web auth is not needed, disable it so the check is skipped
Example fix
// before (.env) BETTER_AUTH_SECRET=changeme // after # openssl rand -base64 32 BETTER_AUTH_SECRET=kJ8fQ2mN7xR4vT9wLpZ3aB6cD1eF0gH5iJ2kL3mN4oP5qR6sT7uV8wX9yZ0aB1c=
Defensive patterns
Strategy: validation
Validate before calling
const MIN = 32; // match MIN_BETTER_AUTH_SECRET_LENGTH
const secret = process.env.BETTER_AUTH_SECRET ?? '';
if (secret.length < MIN) {
throw new Error(`BETTER_AUTH_SECRET must be >= ${MIN} chars: openssl rand -base64 32`);
} Try / catch
try {
await startServer();
} catch (err) {
if (String(err.message).includes('BETTER_AUTH_SECRET')) {
console.error('Set a strong BETTER_AUTH_SECRET before enabling web auth.');
}
throw err;
} Prevention
- Generate the secret at deploy time with `openssl rand -base64 32`; never commit it
- Use a deploy checklist that pairs enabling web auth with provisioning the secret
- Detect placeholder values ('changeme', 'secret') in config linting
When it happens
Trigger: startServer() → assertWebAuthAtBoot() with web auth enabled (per isWebAuthEnabled) and env.BETTER_AUTH_SECRET unset (treated as '') or shorter than the minimum length.
Common situations: Forgetting to set BETTER_AUTH_SECRET in a fresh deployment/.env; placeholder values like 'changeme' or 'secret'; a truncated secret from copy-paste; enabling web auth via a flag without adding the secret env var.
Related errors
- Pi auth: no credentials for provider '${parsed.provider}'. $
- Repository ${owner}/${repo} not found or is private. Check r
- Authentication failed for ${owner}/${repo}. Check GITEA_TOKE
- Checksum mismatch: expected ${expectedHash}, got ${actualHas
- Cannot resolve the project for --adopt/--supersedes. Run fro
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/7a6cd27a687b2db7.
Report an issue: GitHub.