Throw error for invalid extension names (#9538)

This commit is contained in:
christine betts
2025-09-25 14:05:49 -04:00
committed by GitHub
parent defda3a97d
commit 2d76cdf2c6
3 changed files with 44 additions and 1 deletions

View File

@@ -104,7 +104,7 @@ The `gemini-extension.json` file contains the configuration for the extension. T
}
```
- `name`: The name of the extension. This is used to uniquely identify the extension and for conflict resolution when extension commands have the same name as user or project commands. The name should be lowercase and use dashes instead of underscores or spaces. This is how users will refer to your extension in the CLI. Note that we expect this name to match the extension directory name.
- `name`: The name of the extension. This is used to uniquely identify the extension and for conflict resolution when extension commands have the same name as user or project commands. The name should be lowercase or numbers and use dashes instead of underscores or spaces. This is how users will refer to your extension in the CLI. Note that we expect this name to match the extension directory name.
- `version`: The version of the extension.
- `mcpServers`: A map of MCP servers to configure. The key is the name of the server, and the value is the server configuration. These servers will be loaded on startup just like MCP servers configured in a [`settings.json` file](./cli/configuration.md). If both an extension and a `settings.json` file configure an MCP server with the same name, the server defined in the `settings.json` file takes precedence.
- Note that all MCP server configuration options are supported except for `trust`.

View File

@@ -462,6 +462,28 @@ describe('extension tests', () => {
const loadedConfig = extensions[0].config;
expect(loadedConfig.mcpServers?.['test-server'].trust).toBeUndefined();
});
it('should throw an error for invalid extension names', () => {
const consoleSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
const badExtDir = createExtension({
extensionsDir: userExtensionsDir,
name: 'bad_name',
version: '1.0.0',
});
const extension = loadExtension({
extensionDir: badExtDir,
workspaceDir: tempWorkspaceDir,
});
expect(extension).toBeNull();
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('Invalid extension name: "bad_name"'),
);
consoleSpy.mockRestore();
});
});
describe('annotateActiveExtensions', () => {
@@ -951,6 +973,18 @@ This extension will run the following MCP servers:
expect(mockRequestConsent).not.toHaveBeenCalled();
});
it('should throw an error for invalid extension names', async () => {
const sourceExtDir = createExtension({
extensionsDir: tempHomeDir,
name: 'bad_name',
version: '1.0.0',
});
await expect(
installExtension({ source: sourceExtDir, type: 'local' }),
).rejects.toThrow('Invalid extension name: "bad_name"');
});
});
describe('uninstallExtension', () => {

View File

@@ -627,6 +627,14 @@ async function maybeRequestConsentOrFail(
}
}
export function validateName(name: string) {
if (!/^[a-zA-Z0-9-]+$/.test(name)) {
throw new Error(
`Invalid extension name: "${name}". Only letters (a-z, A-Z), numbers (0-9), and dashes (-) are allowed.`,
);
}
}
export function loadExtensionConfig(
context: LoadExtensionContext,
): ExtensionConfig {
@@ -648,6 +656,7 @@ export function loadExtensionConfig(
`Invalid configuration in ${configFilePath}: missing ${!config.name ? '"name"' : '"version"'}`,
);
}
validateName(config.name);
return config;
} catch (e) {
throw new Error(