AI-coded apps

    Does Windsurf Cascade leak .env variables to the client?

    Windsurf Cascade and the question of whether .env variables leak into the client bundle

    This question gets asked by developers who just generated a feature with Windsurf's Cascade agent, glanced at the diff, and noticed their .env file mentioned somewhere along the way. The honest answer separates three different things that often get bundled together under the word 'leak', and only one of them is something the build tool actually does.

    Short answer

    Cascade does not automatically write the contents of your .env file into the browser bundle. The build tool decides what reaches the client: Vite inlines variables prefixed with VITE_, and Next.js inlines anything prefixed with NEXT_PUBLIC_. The risk that does involve Cascade is a separate prompt-injection class disclosed by Embrace The Red in August 2025, where the agent can be tricked into reading .env and shipping it to an attacker server through auto-approved tools.

    What you should know

    • The bundler, not the IDE, decides what ends up in browser JavaScript. Cascade writes the source code; vite build or next build performs the inlining.
    • Build-time inlining is irreversible. Once process.env.NEXT_PUBLIC_X is in your bundle, every browser that loads the page sees that string.
    • Cascade reads files it can see. If .env sits in the workspace and is not blocked, the agent has read access by design.
    • Prompt injection in source files or READMEs can hijack Cascade. A May 2025 disclosure showed unapproved tool calls can exfiltrate .env to attacker servers.
    • .gitignore does not protect a file from Cascade. Git ignores it for commits; Windsurf indexes it for the agent unless you exclude it through .codeiumignore.

    What does "leak to the client" actually mean here?

    The word 'leak' covers three different surfaces, and conflating them is the most common source of confusion in this question.

    The first surface is the browser bundle: the JavaScript that ships to end users. A secret reaches it only if the build tool inlines it, which requires both the right naming prefix and a reference to that name inside client-side code.

    The second surface is the editor's network. Cascade sends portions of your project to Codeium's inference servers to generate completions. The company does not promise that no .env content ever touches that inference path. Treat this as a trusted-vendor relationship, not a zero-knowledge one.

    The third surface is third-party exfiltration. A prompt-injection payload hidden in a dependency, README, or open file can instruct Cascade to call a tool that reaches an attacker server. The data goes through Cascade, not through your build, and the exfiltration is invisible in the diff you review.

    These three surfaces have different fixes. Mixing them produces panic in one direction or false comfort in the other.

    Does Cascade write your .env values into the JavaScript bundle?

    No. Cascade writes TypeScript or JavaScript source code. The bundler reads that source and decides what gets inlined.

    For Vite projects, the official environment variable documentation is explicit: variables prefixed with VITE_ are exposed in client-side source code after Vite bundling. Anything without the prefix stays in process.env on the Node.js side and is stripped from the browser output. The same page adds a direct warning that VITE_* variables should not contain sensitive information such as API keys, because their values are baked into source code at build time.

    For Next.js, the rule has the same shape with a different prefix. The Next.js environment variables guide states that to expose an environment variable to the browser, it must be prefixed with NEXT_PUBLIC_, and these public environment variables are inlined into the JavaScript bundle during next build. Inlining means literal substitution: process.env.NEXT_PUBLIC_ANALYTICS_ID becomes the string "abcdefghijk" in every chunk that referenced it.

    What Cascade can do, and sometimes does, is suggest the wrong prefix. If a generated component reads process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY, the build will obligingly bake the service role key into the client bundle, with no warning. The leak then comes from the prefix choice in the generated code, not from .env itself.

    Has Cascade been caught reading .env files for someone else?

    Yes, in a documented research disclosure. Johann Rehberger of Embrace The Red published a chain in which Cascade's read_url_content tool, which does not require human approval, was used as both a data fetch and a data exfiltration channel. An indirect prompt injection embedded in a project file directed Cascade to read .env and pass its contents as part of an outbound HTTP request to an attacker-controlled server.

    The timeline matters. The disclosure was sent to Windsurf on May 30, 2025; public disclosure followed on August 21, 2025, after the researcher reported receiving no fix ETA. Independent advisories from other firms documented adjacent issues during the same period, including a path-traversal flaw tracked by Tenable as TRA-2025-47 that allowed arbitrary file reads through filename prompt injection.

    The class is 'indirect prompt injection plus auto-approved network tool'. The fix has to come from the vendor: require human approval for outbound HTTP, sandbox file reads to a safe list, or both. Until that lands in a released version, the safe assumption is that any untrusted text Cascade processes can become an instruction.

    How does build-time inlining work in Vite and Next.js?

    The pattern is the same in both. The bundler walks your source code at build time and looks for process.env.X (or import.meta.env.X in Vite). If the variable name matches the prefix policy, the bundler replaces the expression with a literal string. If not, the expression is stripped or left as a Node.js-side reference that never reaches the browser.

    The substitution is purely textual. There is no runtime evaluation, no per-user check, no environment-aware switching after the build. A next build run with NEXT_PUBLIC_STRIPE_SECRET_KEY set in the environment produces JavaScript files where that string is a literal, regardless of who downloads the bundle, what region they are in, or whether they are authenticated.

    This is why the prefix policy matters more than the file location. A .env.local file outside the repository, a CI variable injected at build, and a value pasted into a Vercel dashboard all behave identically once next build runs: the prefix decides, not the source.

    Variable nameSource fileWhere it lives after build
    DATABASE_URL.env.localServer-side process.env, not in browser bundle
    NEXT_PUBLIC_SUPABASE_ANON_KEY.env.localInlined as a literal in every client chunk that references it
    VITE_API_BASE_URL.envInlined as an import.meta.env.VITE_API_BASE_URL substitution
    STRIPE_SECRET_KEY (no prefix).envServer-side only; bundler strips it
    NEXT_PUBLIC_STRIPE_SECRET_KEY (wrong prefix).env.localInlined as a literal, visible to every visitor

    The last row is the realistic failure mode. Cascade does not invent the prefix on purpose, but in a hurry it sometimes mirrors a naming pattern from elsewhere in the codebase, and the build accepts whatever you give it.

    What configuration keeps Cascade away from .env?

    Three controls, in order of effort.

    First, the .codeiumignore file. Windsurf supports a .codeiumignore and respects .gitignore for indexing in recent versions, with caveats around already-indexed files. Adding .env* to .codeiumignore removes the file from the indexed context Cascade uses. The agent can still be asked to read the file directly through a tool call, but the file stops being part of the default context window.

    Second, secret management outside the repository. Move secrets into a vault accessed by your CI (1Password, Doppler, GitHub Actions secrets, Vercel environment settings) and keep only placeholders in .env.example. The agent can read .env.example safely because it contains no real values.

    Third, MCP server hardening. The Windsurf Cascade MCP documentation describes how MCP servers are configured and notes that the filesystem MCP server can restrict access to a list of allowed directories. If you wire third-party MCP servers into Cascade, narrow their access surface. For builders who want an external automated read of their compiled mobile build before submission, PTKD.com (https://ptkd.com) is one of the platforms focused on pre-submission scanning aligned with OWASP MASVS, which catches embedded credentials that survived the AI editor stage and ended up inside an APK, AAB, or IPA.

    What to watch out for

    A first mistake is assuming the question reduces to 'is Cascade malware'. It is not. The risks are structural and shared across AI editors with auto-approved tool access, including the patterns reported in Cursor, Cline, and Google's Antigravity in 2025 research.

    A second mistake is treating the prefix as a safety check. VITE_ and NEXT_PUBLIC_ are inclusion rules, not safety rules. They include any variable with the prefix in the browser bundle. A developer who renames SUPABASE_SERVICE_ROLE_KEY to NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY for convenience has shipped that key to every visitor of the page that references it.

    A third mistake is assuming server-rendered apps avoid the issue. Server-side rendering helps, but any <script> tag populated through templating, any dangerouslySetInnerHTML call with config, or any client component that imports a module that reads the secret pulls the value back into the bundle.

    A fourth mistake is trusting that 'Codeium is a vendor, so this is fine'. Vendor trust is a real layer, but it does not help against prompt injection from a third party inside the editor session. The exfiltration in the Embrace The Red disclosure sent data to an attacker server through Cascade's own approved tools; Codeium was the carrier, not the destination.

    Key takeaways

    • Cascade does not automatically ship .env to the browser; the bundler decides, and only prefixed variables get inlined.
    • The realistic build-time leak is a wrong prefix (NEXT_PUBLIC_, VITE_) on a secret, often suggested by AI completions that mirror existing naming in the repository.
    • The realistic runtime leak is indirect prompt injection through files Cascade processes, documented by Embrace The Red in August 2025, with no patch ETA at disclosure time.
    • Use .codeiumignore, vault-based secret storage, and narrowed MCP server access to reduce the surface; treat .gitignore as orthogonal to IDE indexing.
    • For teams shipping mobile builds with secrets that survived the editor and CI, an external scan of the final APK, AAB, or IPA aligned with OWASP MASVS is a reasonable backstop; platforms like PTKD.com (https://ptkd.com) focus on that pre-submission step for no-code and vibe-coded apps.
    • #security
    • #windsurf
    • #cascade
    • #env-variables
    • #prompt-injection
    • #vibe-coding

    Frequently asked questions

    Did Windsurf fix the prompt-injection exfiltration bug from May 2025?
    At the time of the August 21, 2025 public disclosure by Embrace The Red, Windsurf had acknowledged receipt but had not shipped a documented fix and gave no ETA. Treat the risk as live until the vendor publishes a security advisory naming the affected versions and the patched release. Re-check the Windsurf release notes before relying on the agent inside a repository that contains real secrets.
    If I add .env to .gitignore, does Cascade still see it?
    Yes. The .gitignore file tells Git which files to skip during commits; it does not tell Windsurf which files to skip during indexing. Cascade can still open and read .env when it appears in the workspace. To remove the file from the agent's default context, add it to .codeiumignore, or move the secrets out of the repository into a vault accessed only at build or run time.
    Can a VITE_-prefixed secret stay private if I only use it on the server?
    No. The Vite build inlines every VITE_-prefixed variable that appears in code paths bundled for the browser. If a server-only file imports the same module that reads import.meta.env.VITE_X, the import graph drags it into the client chunk. The Vite documentation states plainly that VITE_ variables should not contain sensitive information, regardless of where you intend to use them.
    Does Cascade send my .env file to Codeium's servers during normal use?
    Codeium sends portions of your project to its inference backend to generate completions. The company does not promise that no .env content ever crosses that path; context selection is heuristic, not guaranteed exclusion. Treat the relationship as ordinary vendor trust, and exclude .env files through .codeiumignore plus vault-based secret storage if your threat model requires it.
    Is the exfiltration risk specific to Windsurf, or does it apply to Cursor and Cline as well?
    Similar indirect prompt-injection patterns have been published against Cursor, Cline, and Google Antigravity through 2025 research. The shared root cause is auto-approved network or file tools combined with agent context that includes untrusted text. The specific tools differ, and so do the disclosed fixes, but the class of risk transfers across AI coding agents that auto-execute reads or fetches without per-call confirmation.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free