diff --git a/src/__tests__/home.test.ts b/src/__tests__/home.test.ts index 4da8376..309bcbf 100644 --- a/src/__tests__/home.test.ts +++ b/src/__tests__/home.test.ts @@ -70,16 +70,16 @@ describe('getUserHome', () => { expect(getUserHome()).toBe(os.homedir()); }); - it('falls back to os.tmpdir when even os.homedir is unresolvable', () => { + it('throws instead of returning a shared/relative dir when home is unresolvable', () => { delete process.env.HOME; delete process.env.USERPROFILE; // os.homedir() is documented to return '' when the home cannot be resolved. + // Falling back to os.tmpdir() (world-writable) or '' (cwd-relative) would be a + // credential-exposure / code-execution vector, so getUserHome() must throw. const spy = vi.spyOn(os, 'homedir').mockReturnValue(''); try { - const home = getUserHome(); - expect(home).toBe(os.tmpdir()); - expect(home).not.toBe(''); + expect(() => getUserHome()).toThrow(/user home directory/); } finally { spy.mockRestore(); } diff --git a/src/utils/home.ts b/src/utils/home.ts index fba4286..cf4a034 100644 --- a/src/utils/home.ts +++ b/src/utils/home.ts @@ -4,16 +4,26 @@ import os from 'node:os'; * Resolve the current user's home directory across supported platforms. * * `HOME` is normally present on Unix-like systems, while a regular Windows - * PowerShell session commonly exposes only `USERPROFILE`. `os.homedir()` is - * the platform-aware fallback, and `os.tmpdir()` is a last resort: `os.homedir()` - * is documented to return `''` when the home cannot be resolved (e.g. a passwd-less - * uid or `env -i`), and callers join this onto `.teamai/...`, so an empty result - * would silently yield a cwd-relative path. Guaranteeing a non-empty absolute path - * keeps every derived path absolute. + * PowerShell session commonly exposes only `USERPROFILE`. `os.homedir()` is the + * platform-aware fallback. + * + * If none of these resolves (os.homedir() is documented to return '' for a + * passwd-less uid or under `env -i`), we throw rather than fall back to a + * shared/relative directory. Callers join this onto `.teamai/...` to write + * credentials and to resolve executables, so a shared location like os.tmpdir() + * would be a code-execution / credential-exposure vector, and an empty string + * would silently produce a cwd-relative path. Failing loudly is the safe choice — + * on any real machine at least one of the three is always set. */ export function getUserHome(): string { - return process.env.HOME?.trim() + const home = process.env.HOME?.trim() || process.env.USERPROFILE?.trim() - || os.homedir() - || os.tmpdir(); + || os.homedir(); + if (!home) { + throw new Error( + 'Unable to determine the user home directory: none of HOME, USERPROFILE, ' + + 'or os.homedir() is available. Set HOME explicitly before running teamai.', + ); + } + return home; }