Skip to content

Commit 912e6f6

Browse files
committed
fix(config): reject empty roots and invalid ports
1 parent a844e53 commit 912e6f6

2 files changed

Lines changed: 42 additions & 3 deletions

File tree

src/server/config.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ export async function loadServerConfig(): Promise<ServerConfig | null> {
5555
result.projects = (config.projects as unknown[])
5656
.filter((p): p is Record<string, unknown> => typeof p === 'object' && p !== null)
5757
.map((p) => {
58-
const rawRoot = typeof p.root === 'string' ? p.root : '';
58+
const rawRoot = typeof p.root === 'string' ? p.root.trim() : '';
59+
if (!rawRoot) {
60+
console.error('[config] Skipping project entry with missing or empty root');
61+
return null;
62+
}
5963
const resolvedRoot = path.resolve(expandTilde(rawRoot));
6064
const proj: ProjectConfig = { root: resolvedRoot };
6165
if (Array.isArray(p.excludePatterns)) {
@@ -64,7 +68,8 @@ export async function loadServerConfig(): Promise<ServerConfig | null> {
6468
);
6569
}
6670
return proj;
67-
});
71+
})
72+
.filter((project): project is ProjectConfig => project !== null);
6873
}
6974

7075
// Resolve server options
@@ -79,7 +84,7 @@ export async function loadServerConfig(): Promise<ServerConfig | null> {
7984
if (srv.port !== undefined) {
8085
const portValue = srv.port;
8186
const portNum = typeof portValue === 'number' ? portValue : Number(portValue);
82-
if (Number.isInteger(portNum) && portNum > 0) {
87+
if (Number.isInteger(portNum) && portNum > 0 && portNum <= 65535) {
8388
result.server.port = portNum;
8489
} else {
8590
console.error(`[config] Ignoring invalid server.port: ${portValue}`);

tests/server-config.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,28 @@ describe('loadServerConfig', () => {
8686
});
8787
});
8888

89+
it('skips project entries with missing or empty roots instead of resolving cwd', async () => {
90+
const errorSpy = vi.spyOn(console, 'error');
91+
const config = JSON.stringify({
92+
projects: [{}, { root: ' ' }, { root: 'valid-root' }]
93+
});
94+
95+
await withTempConfig(config, async (filePath) => {
96+
process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath;
97+
const result = await loadServerConfig();
98+
99+
expect(result).not.toBeNull();
100+
expect(result!.projects).toEqual([{ root: path.resolve('valid-root') }]);
101+
expect(errorSpy).toHaveBeenCalledTimes(2);
102+
expect(errorSpy.mock.calls[0][0]).toMatch(
103+
/\[config\] Skipping project entry with missing or empty root/
104+
);
105+
expect(errorSpy.mock.calls[1][0]).toMatch(
106+
/\[config\] Skipping project entry with missing or empty root/
107+
);
108+
});
109+
});
110+
89111
it('returns valid config for well-formed input with projects and server.port', async () => {
90112
// Use absolute paths that are valid on all platforms
91113
const projA = path.join(os.tmpdir(), 'ccc-test-proj-a');
@@ -147,6 +169,18 @@ describe('loadServerConfig', () => {
147169
});
148170
});
149171

172+
it('drops server.port with a warning when value exceeds 65535', async () => {
173+
const errorSpy = vi.spyOn(console, 'error');
174+
const config = JSON.stringify({ server: { port: 65536 } });
175+
await withTempConfig(config, async (filePath) => {
176+
process.env.CODEBASE_CONTEXT_CONFIG_PATH = filePath;
177+
const result = await loadServerConfig();
178+
expect(result!.server?.port).toBeUndefined();
179+
expect(errorSpy).toHaveBeenCalledOnce();
180+
expect(errorSpy.mock.calls[0][0]).toMatch(/\[config\] Ignoring invalid server\.port: 65536/);
181+
});
182+
});
183+
150184
it('respects CODEBASE_CONTEXT_CONFIG_PATH env var', async () => {
151185
const config = JSON.stringify({ server: { port: 4242 } });
152186
await withTempConfig(config, async (filePath) => {

0 commit comments

Comments
 (0)