Skip to content
Open
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
129 changes: 123 additions & 6 deletions apps/emdash-desktop/src/main/core/ssh/config/sshConfigParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import {
parseSshConfigFileAt,
} from './sshConfigParser';

const itWindows = process.platform === 'win32' ? it : it.skip;
const itPosix = process.platform === 'win32' ? it.skip : it;
const pathSuffix = (suffix: string) => {
const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replaceAll('/', '[\\\\/]');
return new RegExp(`[\\\\/]${escapedSuffix}$`);
};

describe('parseSshConfigContent', () => {
it('lists concrete SSH config aliases with proxy and forwarding preview fields', () => {
const hosts = parseSshConfigContent(`
Expand Down Expand Up @@ -35,8 +42,8 @@ Host plain
hostname: 'dev.internal',
user: 'alice',
port: 2222,
identityFile: expect.stringContaining('/.ssh/dev_ed25519'),
identityAgent: expect.stringContaining('/.1password/agent.sock'),
identityFile: expect.stringMatching(pathSuffix('.ssh/dev_ed25519')),
identityAgent: expect.stringMatching(pathSuffix('.1password/agent.sock')),
proxyCommand: 'cloudflared access ssh --hostname %h',
forwardAgent: true,
forwardAgentValue: '$SSH_AUTH_SOCK',
Expand All @@ -46,8 +53,8 @@ Host plain
hostname: 'dev.internal',
user: 'alice',
port: 2222,
identityFile: expect.stringContaining('/.ssh/dev_ed25519'),
identityAgent: expect.stringContaining('/.1password/agent.sock'),
identityFile: expect.stringMatching(pathSuffix('.ssh/dev_ed25519')),
identityAgent: expect.stringMatching(pathSuffix('.1password/agent.sock')),
proxyCommand: 'cloudflared access ssh --hostname %h',
forwardAgent: true,
forwardAgentValue: '$SSH_AUTH_SOCK',
Expand Down Expand Up @@ -130,6 +137,116 @@ Host included-alias
]);
});

it('expands quoted and unquoted Include glob patterns', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-quoted-'));
await mkdir(join(dir, 'team config'));
await mkdir(join(dir, 'conf.d'));
await writeFile(
join(dir, 'config'),
`
Include "team config/*.conf" conf.d/*.conf
`,
'utf-8'
);
await writeFile(
join(dir, 'team config', 'primary.conf'),
`
Host quoted-include
HostName quoted.internal
`,
'utf-8'
);
await writeFile(
join(dir, 'conf.d', 'secondary.conf'),
`
Host unquoted-include
HostName unquoted.internal
`,
'utf-8'
);

const hosts = await parseSshConfigFileAt(join(dir, 'config'));
expect(hosts).toHaveLength(2);
expect(hosts).toEqual(
expect.arrayContaining([
{
host: 'unquoted-include',
hostname: 'unquoted.internal',
},
{
host: 'quoted-include',
hostname: 'quoted.internal',
},
])
);
});

itWindows('expands Include glob patterns with Windows path separators', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-windows-'));
await mkdir(join(dir, 'absolute'));
await mkdir(join(dir, 'relative'));
await writeFile(
join(dir, 'config'),
`
Include relative\\*.conf "${join(dir, 'absolute', '*.conf')}"
`,
'utf-8'
);
await writeFile(
join(dir, 'absolute', 'team.conf'),
`
Host absolute-windows-include
HostName absolute.internal
`,
'utf-8'
);
await writeFile(
join(dir, 'relative', 'team.conf'),
`
Host relative-windows-include
HostName relative.internal
`,
'utf-8'
);

expect(await parseSshConfigFileAt(join(dir, 'config'))).toEqual([
{
host: 'absolute-windows-include',
hostname: 'absolute.internal',
},
{
host: 'relative-windows-include',
hostname: 'relative.internal',
},
]);
});

itPosix('treats ~\\ as a literal path prefix on POSIX', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-posix-'));
await writeFile(
join(dir, 'config'),
`
Include ~\\*.conf
`,
'utf-8'
);
await writeFile(
join(dir, '~*.conf'),
`
Host posix-escaped-include
HostName posix.internal
`,
'utf-8'
);

expect(await parseSshConfigFileAt(join(dir, 'config'))).toEqual([
{
host: 'posix-escaped-include',
hostname: 'posix.internal',
},
]);
});

it('applies the same included snippet at each Include occurrence', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-repeat-'));
await writeFile(
Expand Down Expand Up @@ -159,13 +276,13 @@ Host second
host: 'first',
hostname: 'first.internal',
user: 'shared',
identityAgent: expect.stringContaining('/.ssh/shared-agent.sock'),
identityAgent: expect.stringMatching(pathSuffix('.ssh/shared-agent.sock')),
},
{
host: 'second',
hostname: 'second.internal',
user: 'shared',
identityAgent: expect.stringContaining('/.ssh/shared-agent.sock'),
identityAgent: expect.stringMatching(pathSuffix('.ssh/shared-agent.sock')),
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ function stripQuotes(value: string): string {
}

/**
* Expands a leading `~` or `~/` to the user's home directory.
* Expands a leading `~`, `~/`, or Windows `~\` to the user's home directory.
*/
function expandTilde(filePath: string): string {
if (filePath === '~') {
return homedir();
}
if (filePath.startsWith('~/')) {
if (filePath.startsWith('~/') || (process.platform === 'win32' && filePath.startsWith('~\\'))) {
return join(homedir(), filePath.slice(2));
}
return filePath;
Expand Down Expand Up @@ -83,7 +83,10 @@ async function resolveIncludePaths(value: string, configDir: string): Promise<st
const absolutePattern = isAbsolute(expandedPattern)
? expandedPattern
: resolve(configDir, expandedPattern);
return await glob(absolutePattern, { nodir: true });
return await glob(absolutePattern, {
nodir: true,
windowsPathsNoEscape: process.platform === 'win32',
});
})
);

Expand Down
Loading