fix(security): require localhost for Claude extension /setup when dashboard auth is disabled (#1270)

* fix(security): restrict Claude extension setup endpoint

* style: apply prettier formatting
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-16 13:58:57 -04:00
1 parent ece43fc30e
commit db175b6807
2 files changed
+52 -4

No files matched your search

@@ -34,9 +34,8 @@ describe('web-server claude-extension-routes', () => {
({ default: SharedManager } = await import('../../../src/management/shared-manager'));
({ createEmptyUnifiedConfig } = await import('../../../src/config/unified-config-types'));
({ saveUnifiedConfig } = await import('../../../src/config/unified-config-loader'));
({ default: claudeExtensionRoutes } = await import(
'../../../src/web-server/routes/claude-extension-routes'
));
({ default: claudeExtensionRoutes } =
await import('../../../src/web-server/routes/claude-extension-routes'));
const app = express();
app.use(express.json());
@@ -68,7 +67,8 @@ describe('web-server claude-extension-routes', () => {
if (originalCcsHome !== undefined) process.env.CCS_HOME = originalCcsHome;
else delete process.env.CCS_HOME;
if (originalClaudeConfigDir !== undefined) process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
if (originalClaudeConfigDir !== undefined)
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
else delete process.env.CLAUDE_CONFIG_DIR;
});
@@ -178,6 +178,47 @@ describe('web-server claude-extension-routes', () => {
expect(payload.sharedSettings.json).toContain('"env"');
});
it('blocks non-local setup requests when dashboard auth is disabled', async () => {
const app = express();
app.use((_req, _res, next) => {
Object.defineProperty(_req.socket, 'remoteAddress', {
configurable: true,
value: '10.0.0.25',
});
next();
});
app.use('/api/claude-extension', claudeExtensionRoutes);
const remoteServer = await new Promise<Server>((resolve, reject) => {
const instance = app.listen(0, '127.0.0.1');
const handleError = (error: Error) => reject(error);
instance.once('error', handleError);
instance.once('listening', () => {
instance.off('error', handleError);
resolve(instance);
});
});
try {
const address = remoteServer.address();
if (!address || typeof address === 'string') {
throw new Error('Unable to resolve remote test server port');
}
const response = await fetch(
`http://127.0.0.1:${address.port}/api/claude-extension/setup?profile=glm&host=vscode`
);
expect(response.status).toBe(403);
const payload = (await response.json()) as { error: string };
expect(payload.error).toBe(
'Claude extension setup requires localhost access when dashboard auth is disabled.'
);
} finally {
await new Promise<void>((resolve) => remoteServer.close(() => resolve()));
}
});
it('normalizes the effective profile CLAUDE_CONFIG_DIR for extension setup', async () => {
const explicitConfigDir = path.join(tempHome, '.claude-profiles', 'glm');
const glmSettingsPath = path.join(tempHome, '.ccs', 'glm.settings.json');