Skip to content

fix(core): close sibling-prefix bypass in get_internal_docs path guard - #29249

Open
ranjan-del wants to merge 1 commit into
google-gemini:mainfrom
ranjan-del:fix/get-internal-docs-path-traversal
Open

fix(core): close sibling-prefix bypass in get_internal_docs path guard#29249
ranjan-del wants to merge 1 commit into
google-gemini:mainfrom
ranjan-del:fix/get-internal-docs-path-traversal

Conversation

@ranjan-del

Copy link
Copy Markdown

Summary

The path traversal guard in the get_internal_docs tool uses a string prefix
comparison, which has no path-component boundary. It therefore accepts any
sibling directory whose name begins with the docs directory name, and the tool
reads that file and returns its contents to the model.

packages/core/src/tools/get-internal-docs.ts:

// Security: Prevent path traversal by resolving and verifying it stays within docsRoot
const resolvedPath = path.resolve(docsRoot, this.params.path);
if (!resolvedPath.startsWith(docsRoot)) {
  throw new Error('Access denied: Requested path is outside the documentation directory.');
}

With a docsRoot of <repo>/docs, a request for ../docs-private/secret.md
resolves to <repo>/docs-private/secret.md. That still starts with
<repo>/docs, so the guard passes.

Details

The fix swaps the prefix comparison for isSubpath() from
packages/core/src/utils/paths.ts, which the repository already uses for
exactly this purpose in services/sandboxedFileSystemService.ts. It compares
using path.relative() instead of a string prefix, so the component boundary
is respected, and it also handles the case-insensitive comparison needed on
Windows and macOS, which a raw startsWith does not.

Why the existing test did not catch it

The current traversal test requests ../package.json. That resolves to
<repo>/package.json, which does not share the <repo>/docs prefix, so it was
already blocked. The sibling-prefix case was simply never exercised.

The new test creates a real sibling of the docs directory containing a marker
file, asserts the request is denied with Access denied, and asserts the marker
content is absent from llmContent. It cleans the directory up in a finally
block. I confirmed it fails on main before the fix, with the tool returning no
error at all.

Scope and severity

Deliberately narrow. This tool is read-only and its reachable surface is limited
to paths that share the docs directory prefix, so this is hardening rather than a
broad disclosure path. I kept the change to the one incorrect comparison rather
than reworking the tool.

Worth noting for a follow-up, not addressed here: the guard resolves with
path.resolve and does not resolve symlinks, so a symlink inside the docs tree
pointing outside it would still be followed. isSubpath alone does not fix that;
it needs resolveToRealPath as well, which is a slightly larger behavioural
change and felt like a separate decision.

Related Issues

No existing issue. Found while investigating #29078. Happy to open one first if
you would prefer the discussion to start there.

How to Validate

On main, the new test fails because the guard lets the sibling path through:

# packages/core
npx vitest run src/tools/get-internal-docs.test.ts
# FAIL ... should prevent access to a sibling directory that shares the docs prefix
#   AssertionError: expected undefined to be defined   <- no error was raised

With this change:

# packages/core
npx vitest run src/tools/get-internal-docs.test.ts
# 5 passed

Full gate:

npm run preflight
# exit 0

Note for reviewers running preflight locally: if your checkout is not a trusted
workspace, 9 tests in packages/cli/src/gemini.test.tsx fail with
FatalUntrustedWorkspaceError regardless of any change. Set
GEMINI_CLI_TRUST_WORKSPACE=true and they pass.

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed) — not needed, no
    user-facing surface change
  • Added/updated tests (if needed) — one regression test, confirmed failing
    before the fix
  • Noted breaking changes (if any) — none. Legitimate paths inside the docs
    directory behave identically
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

Validated on macOS with npm run. isSubpath has its own platform handling and
existing test coverage, so the Windows and Linux risk here is low, but I could
not exercise those hosts myself.

The path traversal guard in the get_internal_docs tool compares with
startsWith():

    const resolvedPath = path.resolve(docsRoot, this.params.path);
    if (!resolvedPath.startsWith(docsRoot)) { ... }

A prefix comparison has no path-component boundary, so it also accepts
any sibling directory whose name begins with the docs directory name.
With a docsRoot of `<repo>/docs`, a request for
`../docs-private/secret.md` resolves to `<repo>/docs-private/secret.md`,
which still starts with `<repo>/docs`, so the guard passes and the file
is read and returned to the model.

The existing traversal test only covered `../package.json`, which
resolves outside the prefix and so was already blocked. It did not
exercise the sibling-prefix case.

Switched to isSubpath() from utils/paths.js, which the repository
already uses for this purpose in services/sandboxedFileSystemService.ts.
It compares with path.relative() rather than a string prefix, so the
component boundary is respected, and it handles the case-insensitive
comparison needed on Windows and macOS.

Added a regression test that creates a real sibling of the docs
directory, asserts the request is denied, and asserts the file's
contents are not returned.
@ranjan-del
ranjan-del requested a review from a team as a code owner September 8, 2026 17:15
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a security vulnerability in the get_internal_docs tool where the path traversal guard was susceptible to bypasses using sibling directories. By switching from a simple startsWith check to the existing isSubpath utility, the tool now correctly respects path component boundaries and handles platform-specific path comparisons, ensuring that file access remains strictly confined to the intended documentation directory.

Highlights

  • Security Hardening: Replaced a flawed string-prefix check with a robust isSubpath utility to prevent path traversal vulnerabilities where sibling directories sharing a common prefix could be accessed.
  • Regression Testing: Added a new integration test that creates a temporary sibling directory to verify that unauthorized access attempts are correctly blocked.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added the size/s A small PR label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

📊 PR Size: size/S

  • Lines changed: 33
  • Additions: +31
  • Deletions: -2
  • Files changed: 2

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🛑 Action Required: Evaluation Approval

Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.

Maintainers:

  1. Go to the Workflow Run Summary.
  2. Click the yellow 'Review deployments' button.
  3. Select the 'eval-gate' environment and click 'Approve'.

Once approved, the evaluation results will be posted here automatically.

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/core Issues related to User Interface, OS Support, Core Functionality labels Sep 8, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves path traversal protection in the GetInternalDocs tool by replacing a simple startsWith check with the isSubpath utility, preventing access to sibling directories sharing a prefix. A corresponding integration test has also been added. The review comments suggest further strengthening this security check by resolving symbolic links using resolveToRealPath to prevent symlink-based path traversal vulnerabilities.

import { ToolErrorType } from './tool-error.js';
import { GET_INTERNAL_DOCS_DEFINITION } from './definitions/coreTools.js';
import { resolveToolDeclaration } from './definitions/resolver.js';
import { isSubpath } from '../utils/paths.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Import resolveToRealPath to resolve symbolic links and prevent symlink-based path traversal vulnerabilities.

Suggested change
import { isSubpath } from '../utils/paths.js';
import { isSubpath, resolveToRealPath } from '../utils/paths.js';
References
  1. Ensure consistent path resolution by using a single, robust function (e.g., resolveToRealPath) for all related path validations.

Comment on lines 128 to +129
const resolvedPath = path.resolve(docsRoot, this.params.path);
if (!resolvedPath.startsWith(docsRoot)) {
if (!isSubpath(docsRoot, resolvedPath)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The current path traversal guard resolves the path using path.resolve but does not resolve symbolic links. If a symbolic link is created inside the documentation directory pointing to a file outside of it, the guard will permit access because the resolved path still appears to be within docsRoot. To prevent symlink-based path traversal vulnerabilities, resolve both docsRoot and the target path to their real paths using resolveToRealPath before performing the subpath check.

      const resolvedPath = resolveToRealPath(path.resolve(docsRoot, this.params.path));
      const realDocsRoot = resolveToRealPath(docsRoot);
      if (!isSubpath(realDocsRoot, resolvedPath)) {
References
  1. Ensure consistent path resolution by using a single, robust function (e.g., resolveToRealPath) for all related path validations.
  2. When requesting file access permissions, resolve symbolic links first to display the actual path being accessed, preventing potential path traversal vulnerabilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality priority/p1 Important and should be addressed in the near term. size/s A small PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant