fix(node-cli): confine agent runtime paths

This commit is contained in:
ZacharyZhang-NY committed 2026-07-19 16:45:42 -04:00
1 parent 3d2dccd8d4
commit 9ae1b77e45
6 files changed
+322 -59

No files matched your search

+147 -1
View File
@@ -1,11 +1,12 @@
import assert from 'node:assert/strict';
import { chmod, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
import { chmod, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { spawn } from 'node:child_process';
import test from 'node:test';
import { resolveInstallSecrets } from '../dist/cli-secrets.js';
import { requirePhysicalDirectory } from '../dist/runtime-paths.js';
const PRIVATE_KEY = Buffer.alloc(32, 7).toString('base64url');
const API_TOKEN = 'ely_test_token';
@@ -102,6 +103,151 @@ test('CLI rejects legacy secret arguments before writing files', async () => {
}
});
test('CLI rejects agent IDs that escape the credential directory', async () => {
const invalidAgentIds = [
'../escape',
'..\\escape',
'C:escape',
'agent.',
'agent ',
' agent',
'CON',
'COM¹.log',
'.',
'..',
];
for (const agentId of invalidAgentIds) {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
try {
const result = await runCli([
'install',
'--agent', 'opencode',
'--org_id', 'org-1',
'--agent_id', agentId,
'--kid', 'key-1',
], homeDir);
assert.equal(result.code, 1);
assert.match(result.stderr, /(Invalid agent ID for local storage|Agent ID escapes)/);
await assert.rejects(stat(path.join(homeDir, '.elydora')), { code: 'ENOENT' });
} finally {
await rm(homeDir, { recursive: true, force: true });
}
}
});
test('uninstall validates the stored runtime identity before plugin changes', async () => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
const runtimeDir = path.join(homeDir, '.elydora', 'stored-directory');
try {
await mkdir(runtimeDir, { recursive: true });
await writeFile(
path.join(runtimeDir, 'config.json'),
JSON.stringify({ agent_name: 'opencode', agent_id: 'different-agent' }),
'utf-8',
);
const result = await runCli(['uninstall', '--agent', 'opencode'], homeDir);
assert.equal(result.code, 1);
assert.match(result.stderr, /crosses its runtime directory/);
assert.equal((await stat(runtimeDir)).isDirectory(), true);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test('uninstall requires an explicit ID for ambiguous agent runtimes', async () => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
try {
for (const agentId of ['agent-1', 'agent-2']) {
const runtimeDir = path.join(homeDir, '.elydora', agentId);
await mkdir(runtimeDir, { recursive: true });
await writeFile(
path.join(runtimeDir, 'config.json'),
JSON.stringify({ agent_name: 'opencode', agent_id: agentId }),
'utf-8',
);
}
const result = await runCli(['uninstall', '--agent', 'opencode'], homeDir);
assert.equal(result.code, 1);
assert.match(result.stderr, /Multiple installed agents/);
assert.equal((await stat(path.join(homeDir, '.elydora', 'agent-1'))).isDirectory(), true);
assert.equal((await stat(path.join(homeDir, '.elydora', 'agent-2'))).isDirectory(), true);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test('runtime directory checks reject symbolic-link children', async (t) => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
const targetDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-target-'));
const runtimeRoot = path.join(homeDir, '.elydora');
const linkPath = path.join(runtimeRoot, 'agent-1');
try {
await mkdir(runtimeRoot, { recursive: true });
try {
await symlink(targetDir, linkPath, 'dir');
} catch (error) {
t.skip(`Directory symbolic links are unavailable: ${error.message}`);
return;
}
await assert.rejects(
requirePhysicalDirectory(linkPath),
/not a physical directory/,
);
} finally {
await rm(homeDir, { recursive: true, force: true });
await rm(targetDir, { recursive: true, force: true });
}
});
test('status surfaces malformed runtime config', async () => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
const runtimeDir = path.join(homeDir, '.elydora', 'agent-1');
try {
await mkdir(runtimeDir, { recursive: true });
await writeFile(path.join(runtimeDir, 'config.json'), '{invalid-json', 'utf-8');
const result = await runCli(['status'], homeDir);
assert.equal(result.code, 1);
assert.match(result.stderr, /Could not parse agent config/);
} finally {
await rm(homeDir, { recursive: true, force: true });
}
});
test('status rejects symbolic-link runtime configs', async (t) => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
const targetDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-target-'));
const runtimeDir = path.join(homeDir, '.elydora', 'agent-1');
const targetConfig = path.join(targetDir, 'config.json');
try {
await mkdir(runtimeDir, { recursive: true });
await writeFile(
targetConfig,
JSON.stringify({ agent_name: 'opencode', agent_id: 'agent-1' }),
'utf-8',
);
try {
await symlink(targetConfig, path.join(runtimeDir, 'config.json'), 'file');
} catch (error) {
t.skip(`File symbolic links are unavailable: ${error.message}`);
return;
}
const result = await runCli(['status'], homeDir);
assert.equal(result.code, 1);
assert.match(result.stderr, /runtime config is not a physical file/);
} finally {
await rm(homeDir, { recursive: true, force: true });
await rm(targetDir, { recursive: true, force: true });
}
});
test('CLI installs from owner-only files and protects persisted credentials', async () => {
const homeDir = await mkdtemp(path.join(os.tmpdir(), 'elydora-node-cli-'));
const privateKeyFile = path.join(homeDir, 'private-key');