Skip to content

Server-only env vars are exposed to client code #385

@yamcodes

Description

@yamcodes

Problem

The Vite plugin currently exposes all environment variables from the schema to client code, including server-only variables like PORT. This happens because the plugin doesn't filter to only VITE_* prefixed variables.

Current Behavior

In apps/playgrounds/vite/vite.config.ts, a single schema Env is reused for both:

  1. Server-side validation (via arkenv(Env, loadEnv(...)))
  2. Client-side exposure (via arkenvVitePlugin(Env))

The schema includes:

  • PORT (server-only, unprefixed)
  • VITE_MY_VAR, VITE_MY_NUMBER, VITE_MY_BOOLEAN (client-safe, prefixed)

However, the plugin implementation in packages/vite-plugin/src/index.ts does:

const env = createEnv(options, loadEnv(...))
const define = Object.fromEntries(
  Object.entries(env).map(([key, value]) => [
    `import.meta.env.${key}`,
    JSON.stringify(value),
  ])
);

This means every key in the schema (including PORT) ends up as import.meta.env.* in the client bundle, not just the VITE_* ones.

Impact

  • Security risk: Server-only variables (like PORT, database URLs, API keys, etc.) can be accidentally exposed to client code
  • Misleading documentation: The comment in vite.config.ts says unprefixed variables are "server-only and not exposed to client code", but this is not true
  • User confusion: Users following the example pattern may accidentally expose sensitive variables in production

Evidence

You can see PORT is accessible in the client in apps/playgrounds/vite/src/App.tsx:

<p>My port: {String(import.meta.env.PORT)}</p>

Proposed Solutions

Choose one of the following approaches:

Option 1: Split the schema (Recommended)

  • Create a ServerEnv schema (e.g., PORT) for use with arkenv()
  • Create a ClientEnv schema (only VITE_* variables) for use with arkenvVitePlugin()
  • Update the example to show this pattern

Option 2: Filter in the plugin

  • Modify arkenvVitePlugin to automatically filter to only VITE_* prefixed keys
  • Update documentation and comments to reflect this behavior
  • This would make the plugin safer by default

Option 3: Update example only

  • Keep Env client-only in the example
  • Introduce a separate server-only schema in documentation
  • This is the simplest fix but doesn't solve the underlying plugin behavior

Additional Note

For VITE_MY_BOOLEAN, the current implementation coerces anything other than "true" to false. If you want stricter validation, consider explicitly validating "true"/"false" and throwing an error on invalid values rather than silently coercing.

Files Affected

  • apps/playgrounds/vite/vite.config.ts (lines 10-15, 19-20, 28-30)
  • packages/vite-plugin/src/index.ts (lines 23, 26-31)
  • apps/playgrounds/vite/src/App.tsx (lines 44-48 - test code showing the leak)

Metadata

Metadata

Assignees

No one assigned

    Labels

    @arkenv/vite-pluginIssues or Pull Requests involving the Vite plugin for ArkEnvbugSomething isn't workingsecurityThis has security implications and should be highly prioritized

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions