Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/__tests__/home.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
28 changes: 19 additions & 9 deletions src/utils/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading