Skip to content

Commit d862e0a

Browse files
fix: strip quoted env var values before Zod validation
Infisical export and some .env loaders produce values wrapped in single quotes (e.g. NEXT_PUBLIC_SUPPORT_EMAIL='support@codebuff.com'), causing Zod format validators (z.email(), z.url()) to reject them. - Add stripEnvQuotes() to common/src/env-schema.ts so clientProcessEnv values are sanitised before schema validation runs - Add the same stripping logic to sdk/test/setup-env.ts preload so test environment values are also cleaned up early Co-Authored-By: James <james@codebuff.com>
1 parent 6d8bf39 commit d862e0a

File tree

2 files changed

+52
-9
lines changed

2 files changed

+52
-9
lines changed

common/src/env-schema.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,37 @@ export type ClientInput = {
2020
}
2121
export type ClientEnv = z.infer<typeof clientEnvSchema>
2222

23+
/**
24+
* Strip wrapping single or double quotes from an env var value.
25+
* Some secret managers (e.g. Infisical export) produce values like:
26+
* NEXT_PUBLIC_SUPPORT_EMAIL='support@codebuff.com'
27+
* which sets the value to the literal string `'support@codebuff.com'`
28+
* (with quotes), causing Zod format validators (z.email(), z.url()) to fail.
29+
*/
30+
function stripEnvQuotes(value: string | undefined): string | undefined {
31+
if (
32+
value &&
33+
value.length >= 2 &&
34+
((value.startsWith("'") && value.endsWith("'")) ||
35+
(value.startsWith('"') && value.endsWith('"')))
36+
) {
37+
return value.slice(1, -1)
38+
}
39+
return value
40+
}
41+
2342
// Bun will inject all these values, so we need to reference them individually (no for-loops)
2443
export const clientProcessEnv: ClientInput = {
25-
NEXT_PUBLIC_CB_ENVIRONMENT: process.env.NEXT_PUBLIC_CB_ENVIRONMENT,
26-
NEXT_PUBLIC_CODEBUFF_APP_URL: process.env.NEXT_PUBLIC_CODEBUFF_APP_URL,
27-
NEXT_PUBLIC_SUPPORT_EMAIL: process.env.NEXT_PUBLIC_SUPPORT_EMAIL,
28-
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
29-
NEXT_PUBLIC_POSTHOG_HOST_URL: process.env.NEXT_PUBLIC_POSTHOG_HOST_URL,
44+
NEXT_PUBLIC_CB_ENVIRONMENT: stripEnvQuotes(process.env.NEXT_PUBLIC_CB_ENVIRONMENT),
45+
NEXT_PUBLIC_CODEBUFF_APP_URL: stripEnvQuotes(process.env.NEXT_PUBLIC_CODEBUFF_APP_URL),
46+
NEXT_PUBLIC_SUPPORT_EMAIL: stripEnvQuotes(process.env.NEXT_PUBLIC_SUPPORT_EMAIL),
47+
NEXT_PUBLIC_POSTHOG_API_KEY: stripEnvQuotes(process.env.NEXT_PUBLIC_POSTHOG_API_KEY),
48+
NEXT_PUBLIC_POSTHOG_HOST_URL: stripEnvQuotes(process.env.NEXT_PUBLIC_POSTHOG_HOST_URL),
3049
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY:
31-
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,
50+
stripEnvQuotes(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY),
3251
NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL:
33-
process.env.NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL,
52+
stripEnvQuotes(process.env.NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL),
3453
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID:
35-
process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID,
36-
NEXT_PUBLIC_WEB_PORT: process.env.NEXT_PUBLIC_WEB_PORT,
54+
stripEnvQuotes(process.env.NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION_ID),
55+
NEXT_PUBLIC_WEB_PORT: stripEnvQuotes(process.env.NEXT_PUBLIC_WEB_PORT),
3756
}

sdk/test/setup-env.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@ const serverDefaults: Record<string, string> = {
3333
DISCORD_APPLICATION_ID: 'test',
3434
}
3535

36+
/**
37+
* Strip wrapping single or double quotes from env var values.
38+
* Infisical export and some .env loaders may produce values like:
39+
* NEXT_PUBLIC_SUPPORT_EMAIL='support@codebuff.com'
40+
* which sets the value to the literal string `'support@codebuff.com'`
41+
* (with quotes), causing Zod format validators (z.email(), z.url()) to fail.
42+
*/
43+
function stripEnvQuotes(defaults: Record<string, string>) {
44+
for (const key of Object.keys(defaults)) {
45+
const value = process.env[key]
46+
if (
47+
value &&
48+
value.length >= 2 &&
49+
((value.startsWith("'") && value.endsWith("'")) ||
50+
(value.startsWith('"') && value.endsWith('"')))
51+
) {
52+
process.env[key] = value.slice(1, -1)
53+
}
54+
}
55+
}
56+
57+
stripEnvQuotes(testDefaults)
58+
stripEnvQuotes(serverDefaults)
59+
3660
for (const [key, value] of Object.entries(testDefaults)) {
3761
if (!process.env[key]) {
3862
process.env[key] = value

0 commit comments

Comments
 (0)