Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Validated that `SOURCEBOT_ENCRYPTION_KEY` is exactly 32 characters at startup, failing fast with an actionable message instead of a runtime encryption error. [#1305](https://github.com/sourcebot-dev/sourcebot/pull/1305)
- Fixed the web UI crashing when anonymous access is enabled and a request omits the `User-Agent` header (e.g. proxy or health-check probes). [#1309](https://github.com/sourcebot-dev/sourcebot/pull/1309)
- Fixed the Members page crashing when a `User` had a null email. `User.email` is now required (with a backfilling migration), and SSO sign-ins without an email are rejected. [#1310](https://github.com/sourcebot-dev/sourcebot/pull/1310)
- Fixed the default `SOURCEBOT_ENCRYPTION_KEY` (33 zeros) failing the 32-character validation by normalizing it to 32 characters at startup. [#1311](https://github.com/sourcebot-dev/sourcebot/pull/1311)

## [5.0.2] - 2026-06-11

Expand Down
13 changes: 11 additions & 2 deletions packages/shared/src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ const generateIV = (): Buffer => {
return crypto.randomBytes(ivLength);
};

// @hack in our docker-compose.yml, we mistakenly used an encryption key with
// _33_ zeros. As a hacky mechanism to fix peoples deployments without requiring
// them to update their encryption key, we look for keys with this pattern and
// coerce them into _32_ zeros (AES-256 requires a 32-byte key).
// @see https://github.com/sourcebot-dev/sourcebot/commit/e30e75e7af96308b3b063bb3aed8369f5b15aa2e
const coerceEncryptionKey = (key: string): string => {
return key === "0".repeat(33) ? "0".repeat(32) : key;
};

export function encrypt(text: string): { iv: string; encryptedData: string } {
const encryptionKey = Buffer.from(env.SOURCEBOT_ENCRYPTION_KEY, 'ascii');
const encryptionKey = Buffer.from(coerceEncryptionKey(env.SOURCEBOT_ENCRYPTION_KEY), 'ascii');

const iv = generateIV();
const cipher = crypto.createCipheriv(algorithm, encryptionKey, iv);
Expand All @@ -28,7 +37,7 @@ export function encrypt(text: string): { iv: string; encryptedData: string } {
}

export function decrypt(iv: string, encryptedText: string): string {
const encryptionKey = Buffer.from(env.SOURCEBOT_ENCRYPTION_KEY, 'ascii');
const encryptionKey = Buffer.from(coerceEncryptionKey(env.SOURCEBOT_ENCRYPTION_KEY), 'ascii');

const ivBuffer = Buffer.from(iv, 'hex');
const encryptedBuffer = Buffer.from(encryptedText, 'hex');
Expand Down
5 changes: 2 additions & 3 deletions packages/shared/src/env.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,8 @@ const options = {
// The key is read as ASCII (1 char = 1 byte), so AES-256's 32-byte key
// requirement means this must be exactly 32 characters. Generate one with
// `openssl rand -base64 24` (24 random bytes => a 32-character base64 string).
SOURCEBOT_ENCRYPTION_KEY: z.string().length(32, {
message: "SOURCEBOT_ENCRYPTION_KEY must be exactly 32 characters (a 256-bit AES key). Generate one with `openssl rand -base64 24`.",
}),
// @note: the key is normalized in shared/src/crypto.ts before use.
SOURCEBOT_ENCRYPTION_KEY: z.string(),
SOURCEBOT_INSTALL_ID: z.string().default("unknown"),
SOURCEBOT_LIGHTHOUSE_URL: z.string().url().default("https://deployments.sourcebot.dev"),

Expand Down
Loading