History

Filter app runtime env vars from terminal spawn environment

- build terminal spawn env from a filtered copy of process env - exclude `PORT`, `T3CODE_*`, `VITE_*`, and Electron runtime vars - add test coverage to verify filtered and preserved env keys

Julius Marminge committed 9 months ago

Commit ecea6571 parent 9e3fa3e2 files · +73 −1Browse files

Unified · Split

2 files
  1. apps/server/src/terminalManager.test.ts +45 −0
  2. apps/server/src/terminalManager.ts +28 −1
apps/server/src/terminalManager.test.ts+45−0
@@ -424,4 +424,49 @@ describe("TerminalManager", () => { · 423 unmodified lines
424 424
425 manager.dispose();425 manager.dispose();
426 });426 });
427
428 it("filters app runtime env variables from terminal sessions", async () => {
429 const originalValues = new Map<string, string | undefined>();
430 const setEnv = (key: string, value: string | undefined) => {
431 if (!originalValues.has(key)) {
432 originalValues.set(key, process.env[key]);
433 }
434 if (value === undefined) {
435 delete process.env[key];
436 return;
437 }
438 process.env[key] = value;
439 };
440 const restoreEnv = () => {
441 for (const [key, value] of originalValues) {
442 if (value === undefined) {
443 delete process.env[key];
444 } else {
445 process.env[key] = value;
446 }
447 }
448 };
449
450 setEnv("PORT", "5173");
451 setEnv("T3CODE_PORT", "3773");
452 setEnv("VITE_DEV_SERVER_URL", "http://localhost:5173");
453 setEnv("TEST_TERMINAL_KEEP", "keep-me");
454
455 try {
456 const { manager, ptyAdapter } = makeManager();
457 await manager.open(openInput());
458 const spawnInput = ptyAdapter.spawnInputs[0];
459 expect(spawnInput).toBeDefined();
460 if (!spawnInput) return;
461
462 expect(spawnInput.env.PORT).toBeUndefined();
463 expect(spawnInput.env.T3CODE_PORT).toBeUndefined();
464 expect(spawnInput.env.VITE_DEV_SERVER_URL).toBeUndefined();
465 expect(spawnInput.env.TEST_TERMINAL_KEEP).toBe("keep-me");
466
467 manager.dispose();
468 } finally {
469 restoreEnv();
470 }
471 });
427});472});
apps/server/src/terminalManager.ts+28−1
@@ -26,6 +26,11 @@ import { runProcess } from "./processRunner"; · 25 unmodified lines
26const DEFAULT_HISTORY_LINE_LIMIT = 5_000;26const DEFAULT_HISTORY_LINE_LIMIT = 5_000;
27const DEFAULT_PERSIST_DEBOUNCE_MS = 40;27const DEFAULT_PERSIST_DEBOUNCE_MS = 40;
28const DEFAULT_SUBPROCESS_POLL_INTERVAL_MS = 1_000;28const DEFAULT_SUBPROCESS_POLL_INTERVAL_MS = 1_000;
29const TERMINAL_ENV_BLOCKLIST = new Set([
30 "PORT",
31 "ELECTRON_RENDERER_PORT",
32 "ELECTRON_RUN_AS_NODE",
33]);
29 34
30type TerminalSubprocessChecker = (terminalPid: number) => Promise<boolean>;35type TerminalSubprocessChecker = (terminalPid: number) => Promise<boolean>;
31 36
@@ -234,6 +239,27 @@ function toSessionKey(threadId: string, terminalId: string): string { · 202 unmodified lines
234 return `${threadId}\u0000${terminalId}`;239 return `${threadId}\u0000${terminalId}`;
235}240}
236 241
242function shouldExcludeTerminalEnvKey(key: string): boolean {
243 const normalizedKey = key.toUpperCase();
244 if (normalizedKey.startsWith("T3CODE_")) {
245 return true;
246 }
247 if (normalizedKey.startsWith("VITE_")) {
248 return true;
249 }
250 return TERMINAL_ENV_BLOCKLIST.has(normalizedKey);
251}
252
253function createTerminalSpawnEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
254 const spawnEnv: NodeJS.ProcessEnv = {};
255 for (const [key, value] of Object.entries(baseEnv)) {
256 if (value === undefined) continue;
257 if (shouldExcludeTerminalEnvKey(key)) continue;
258 spawnEnv[key] = value;
259 }
260 return spawnEnv;
261}
262
237export class TerminalManager extends EventEmitter<TerminalManagerEvents> {263export class TerminalManager extends EventEmitter<TerminalManagerEvents> {
238 private readonly sessions = new Map<string, TerminalSessionState>();264 private readonly sessions = new Map<string, TerminalSessionState>();
239 private readonly logsDir: string;265 private readonly logsDir: string;
@@ -467,6 +493,7 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> { · 227 unmodified lines
467 let startedShell: string | null = null;493 let startedShell: string | null = null;
468 try {494 try {
469 const shellCandidates = resolveShellCandidates(this.shellResolver);495 const shellCandidates = resolveShellCandidates(this.shellResolver);
496 const terminalEnv = createTerminalSpawnEnv(process.env);
470 let lastSpawnError: unknown = null;497 let lastSpawnError: unknown = null;
471 498
472 for (const shell of shellCandidates) {499 for (const shell of shellCandidates) {
@@ -476,7 +503,7 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> { · 3 unmodified lines
476 cwd: session.cwd,503 cwd: session.cwd,
477 cols: session.cols,504 cols: session.cols,
478 rows: session.rows,505 rows: session.rows,
479 env: process.env,506 env: terminalEnv,
480 });507 });
481 startedShell = shell;508 startedShell = shell;
482 break;509 break;