google-gemini / google-gemini/gemini-cli
security(extensions): extension name path traversal allows write outside extensions directory
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
In `packages/cli/src/config/extension-manager.ts` and `packages/cli/src/config/extensions/storage.ts` (main @ 812f7a2bc), extension installation uses the extension's `name` field directly as a filesystem path component without sufficient sanitization:
`storage.ts` and `extension-manager.ts`:
```ts
const extensionPath = path.join(extensionsDir, extensionName);
await fs.promises.cp(source, extensionPath, { recursive: true });
```
While `validateName` checks `/^[a-zA-Z0-9-]+$/`, this validation is only applied during `inferInstallMetadata` for GitHub sources, not for local filesystem sources or when `extension.json` is manually crafted. An extension with `name: "../../.ssh"` or `name: "evil/subdir"` in its `extension.json` could cause the install to write outside the intended `~/.gemini/extensions` directory.
Additionally, `copyExtension` uses `fs.promises.cp` with `recursive: true` without verifying that `source` is within expected bounds, and `makeWritableRecursive` follows symlinks without limit, enabling symlink attacks to overwrite arbitrary files.
A malicious extension (e.g., from a compromised git repository) could declare:
```json
{
"name": "../.config/autostart",
"version": "1.0.0"
}
```
and upon `gemini extensions install` or auto-update, write files to `~/.config/autostart` or similar sensitive locations.
### What did you expect to happen?
- `extensionName` should be strictly validated against path traversal (`..`, `/`, `\`) at all entry points, not just GitHub inference, with explicit rejection if `path.join(extensionsDir, name)` resolves outside `extensionsDir` (via `isWithinRoot` check)
- `copyExtension` should verify `source` is within expected temp directory and `destination` is within extensions directory before copying
- `makeWritableRecursive` should limit symlink depth and not follow symlinks that escape the destination
### Client information
- Source-level finding verified against upstream `main` at commit `812f7a2bc`
- Files: `packages/cli/src/config/extension-manager.ts:1266-1272`, `packages/cli/src/config/extensions/storage.ts`, `packages/cli/src/config/extension.ts`
- Affects all platforms
### Login information
Not applicable.
### Anything else we need to know?
Sources:
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/config/extension-manager.ts#L1266-L1272
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/config/extensions/storage.ts
- `validateName` regex exists but not consistently enforced
**Repro:**
1. Create a local extension dir with `extension.json` containing `{"name": "../evil", "version": "1.0.0"}`
2. `gemini extensions install /path/to/evil-extension`
3. Observe files written to parent directory of extensions dir
Searched existing issues for "extension path traversal", "extension name validation" — no open duplicate found.
Contributor guide
Research direction
Start in packages/cli/src/config/extension-manager.ts around lines 1266-1272, then trace extension metadata handling in packages/cli/src/config/extension.ts and copying in packages/cli/src/config/extensions/storage.ts. Reproduce the local extension case, then verify invalid names, source and destination boundaries, and symlink handling prevent writes outside the extensions directory.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100