Skip to content
Source
Sign inSign up

[Bug]: Lowercase Windows T3CODE_HOME survives the WSL backend isolation filter #35961

Send to my agent
Closedcoygeek(GitHub profile, opens in a new tab) opened this issue 1 comment

Summary

The desktop's WSL launcher fails to remove the Windows storage-home setting when its environment key uses a different letter case. The uppercase spelling is removed, but a lowercase alias remains available to WSL environment forwarding, defeating the launcher's explicit separation of Windows and Linux state. A reduced probe executing the checked-in builder reproduces that difference; no Windows launch or database access was performed.

Before submitting

  • Related issues: searched open and closed issues and PRs for WSL home and environment casing; no report of this filter bypass was found.
  • Removed credentials, tokens, private paths, and sensitive data.

Area

apps/desktop

Observed behavior

For a synthetic Windows environment containing t3code_home=C:\\t3-state and WSLENV=T3CODE_HOME/p, the WSL builder's final child environment still contains t3code_home with that value, and retains T3CODE_HOME/p in WSLENV. Its otherwise equivalent uppercase-key control removes the host home correctly. Windows resolves the key case-insensitively, so leaving the lowercase property does not remove the Windows variable from wsl.exe's environment. This is an observed source-derived builder result; actual WSL translation and the final Linux home were not run.

Reproduction

  1. Use a clean checkout of the commit below and save the script below as repro-home.mjs outside the checkout.
  2. Run node repro-home.mjs /path/to/checkout. The script supplies a Windows environment with only a synthetic host home and its WSLENV path-forwarding entry.
  3. The uppercase control removes the host home. The lowercase case leaves it in the final child environment while keeping its forwarding entry.

This reduced configuration case reproduced deterministically once; an earlier combined exploration showed the same difference. No dependency installation, live state, credentials, Windows host, or WSL distro is required. The Windows process.env lookup is modeled with a proxy that preserves the original key spelling during enumeration. The production filter and resulting environment expression execute directly from the checkout after TypeScript annotation stripping; this does not instantiate the full Effect service or spawn WSL.

The script prints its Node and Git versions, uses temporary home/state directories, removes them, and leaves the checkout untouched. Exit 1 means a case-insensitive alias for the host home remains, 0 means it is absent, and 2 means a control or setup failed. Source-layout changes that prevent extraction are inconclusive.

Reproduction script: repro-home.mjs
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import vm from 'node:vm';
import { stripTypeScriptTypes } from 'node:module';
import { execFileSync } from 'node:child_process';
console.log(`node ${process.version}; ${process.platform} ${process.arch}`);
const checkout = path.resolve(process.argv[2]);
console.log(execFileSync('git',['--version'],{encoding:'utf8'}).trim());
console.log(`commit ${execFileSync('git', ['-C', checkout, 'rev-parse', 'HEAD'], {encoding:'utf8'}).trim()}`);
const source = fs.readFileSync(path.join(checkout, 'apps/desktop/src/backend/DesktopBackendConfiguration.ts'), 'utf8');
const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'wsl-env-probe-'));
try {
  for (const key of ['HOME','USERPROFILE','APPDATA','LOCALAPPDATA','XDG_CONFIG_HOME','XDG_DATA_HOME','XDG_STATE_HOME','XDG_CACHE_HOME','T3CODE_HOME','TMPDIR']) {
    const dir=path.join(scratch,key); fs.mkdirSync(dir); process.env[key]=dir;
  }
  const cut = (start,end) => {
    const i=source.indexOf(start), j=source.indexOf(end,i);
    if(i<0||j<0) throw Error('source layout changed');
    return source.slice(i,j);
  };
  const helpers=cut('const DESKTOP_BACKEND_ENV_NAMES =','const logBackendObservabilitySettingsReadFailure =');
  const builder=cut('  const forwardedEnv: Record<string, string> = {};','  const baseConfig = {');
  const envExpression=cut('    env: {\n      ...parentEnvWithoutT3Home,','    // env already excludes T3CODE_HOME;').replace(/^    env: /,'').trim().replace(/,$/,'');
  const api=vm.runInNewContext(stripTypeScriptTypes(`${helpers}\n({mergeWslEnv, build(env: Record<string,string|undefined>) {${builder}\nreturn ${envExpression};}})`,{mode:'strip'}));
  const lookup = (object,key) => object[Object.keys(object).find(k=>k.toUpperCase()===key.toUpperCase())];
  // Models Windows process.env lookup while preserving its enumerated key spelling.
  const windowsEnv = values=>new Proxy(values,{get:(object,key)=> typeof key==='string'?lookup(object,key):object[key]});
  const control=api.build(windowsEnv({T3CODE_HOME:'C:\\t3-state',WSLENV:'T3CODE_HOME/p'}));
  if(Object.keys(control).some(k=>k.toUpperCase()==='T3CODE_HOME')) throw Error('uppercase home control failed');
  console.log('CONTROL T3CODE_HOME removed=true');
  const lowercase=api.build(windowsEnv({t3code_home:'C:\\t3-state',WSLENV:'T3CODE_HOME/p'}));
  const remaining=Object.keys(lowercase).filter(k=>k.toUpperCase()==='T3CODE_HOME');
  console.log(`CASE surviving home keys=${remaining.join(',')}`);
  console.log(`CASE surviving Windows home=${lookup(lowercase,'T3CODE_HOME')}; WSLENV=${lowercase.WSLENV}`);
  const failed=remaining.length>0;
  console.log(failed?'RESULT: defect reproduced':'RESULT: expected behavior');
  process.exitCode=failed?1:0;
} catch(error) {
  console.log(`setup/control failure: ${error.message}`);
  console.log('RESULT: inconclusive'); process.exitCode=2;
} finally {fs.rmSync(scratch,{recursive:true,force:true});}

Expected behavior

The Windows T3 home must be absent from the WSL child environment regardless of the host key's letter case. The code explicitly omits t3Home from the WSL bootstrap to keep the Linux backend from sharing Windows SQLite state in apps/desktop/src/backend/DesktopBackendConfiguration.ts#L562-L578, and sets extendEnv: false specifically to prevent restoring the stripped home in apps/desktop/src/backend/DesktopBackendConfiguration.ts#L651-L670. Node documents case-insensitive Windows environment lookup in process.env. Microsoft documents the Windows-to-WSL forwarding and path-translation behavior in WSLENV flags.

Impact and scope

Impact: P2 - A supported Windows key spelling defeats the builder's explicit host-home isolation and leaves a configured Windows storage-home value available to the Linux backend's forwarding path.

The affected input and surviving child-environment property are demonstrated. Recreating the Windows variable with the uppercase spelling T3CODE_HOME is a bounded configuration workaround at this boundary; the control shows that spelling is removed. NOT_ENOUGH_INFO: the final Linux T3CODE_HOME, database selection, and any concurrent Windows/Linux file access were not observed because no Windows host was available. An end-to-end check with a disposable Windows home and the shown forwarding entry would establish those effects. This report does not claim that data corruption occurred.

Version

T3 Code commit 59e64eef881632a23f89c4138bcf37f23d92bf93. The repository's Electron pin is 44.10.1; no dependency pins were changed.

Runtime and platform

Probe: Node v26.12.1, Git 2.56.0, macOS (darwin), arm64. Affected target: Windows desktop WSL backend. NOT_ENOUGH_INFO: no installed Windows/WSL/distro runtime version was available; the observed result is at the reduced configuration-builder boundary.

Interface

Other: desktop WSL backend child environment and storage-home isolation.

Earliest failure stage

Setup or configuration.

Relevant output or evidence

node v26.12.1; darwin arm64
git version 2.56.0
commit 59e64eef881632a23f89c4138bcf37f23d92bf93
CONTROL T3CODE_HOME removed=true
CASE surviving home keys=t3code_home
CASE surviving Windows home=C:\\t3-state; WSLENV=T3CODE_HOME/p
RESULT: defect reproduced

Node also prints its standard experimental warning for stripTypeScriptTypes; it does not affect the result. Every environment value is synthetic.

The host environment comes directly from process.env in packages/shared/src/HostProcess.ts#L61-L66. The filter compares each enumerated key with the exact uppercase string T3CODE_HOME before copying it to the final WSL child environment in apps/desktop/src/backend/DesktopBackendConfiguration.ts#L651-L670. The Linux server accepts T3CODE_HOME as its storage-home configuration in apps/server/src/cli/config.ts#L136-L140.

  1. tanstack-source added labelsbug

  2. juliusmarmingecommented

    I tried to reproduce this on the current build and could not.

    Commit 67d91a0390 (origin/main). No Windows host or WSL distro is available, so wsl.exe was never launched. Instead the real DesktopBackendConfiguration.resolveWsl service ran in its unit test harness (platform win32, test WSL layers). The existing test 'resolveWsl preserves existing WSLENV entries when forwarding backend secrets' was given t3code_home: "C:\t3-state" in the host environment, plus an assertion that no key in config.env matches T3CODE_HOME case-insensitively. vp test run src/backend/DesktopBackendConfiguration.test.ts in apps/desktop fails with expected [ 't3code_home' ] to deeply equal [], so the lowercase key reaches the env built for wsl.exe; the other 16 tests pass. The reporter's own probe also kept the WSLENV entry T3CODE_HOME/p. Neither of us observed what home the Linux backend actually picks.

    Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.

  3. juliusmarminge closed this

Sign in to join this conversation.