Skip to content

Vulnerability bigquery rest traversal - #9188

Draft
danieljbruce wants to merge 24 commits into
mainfrom
vulnerability-bigquery-rest-traversal
Draft

Vulnerability bigquery rest traversal#9188
danieljbruce wants to merge 24 commits into
mainfrom
vulnerability-bigquery-rest-traversal

Conversation

@danieljbruce

Copy link
Copy Markdown
Contributor

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

@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 introduces URI encoding and path traversal validation utilities to secure service requests against directory traversal attacks. However, the security review identified critical vulnerabilities where percent-encoded sequences (e.g., %2e%2e) can bypass the validation checks in validateUriPathSegment and validateUriPath. Additionally, encodeURIPath may double-encode already percent-encoded segments. To address these issues, it is recommended to decode path segments using decodeURIComponent before performing validation and encoding.

Comment thread core/common/src/util.ts
Comment on lines +1029 to +1033
export function validateUriPathSegment(propertyName: string, value: string): void {
if (value === '.' || value === '..') {
throw new Error(`Invalid value ${value} for ${propertyName}`);
}
}

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

validateUriPathSegment does not decode the input segment before checking if it is . or ... If a user passes a percent-encoded path traversal sequence (e.g., %2e%2e), this check will be bypassed. To prevent path traversal bypasses, the segment should be decoded using decodeURIComponent before validation.

export function validateUriPathSegment(propertyName: string, value: string): void {
  try {
    const decoded = decodeURIComponent(value);
    if (decoded === '.' || decoded === '..') {
      throw new Error(`Invalid value ${value} for ${propertyName}`);
    }
  } catch {
    if (value === '.' || value === '..') {
      throw new Error(`Invalid value ${value} for ${propertyName}`);
    }
  }
}

Comment thread core/common/src/util.ts
Comment on lines +1039 to +1051
export function validateUriPath(propertyName: string, value: string): void {
if (value) {
// Split by slash and check for exact segment matches of '.' or '..' rather
// than using a simple string.includes('.') check. This avoids rejecting
// valid domain-scoped resource segments (e.g. projects/example.com:project-id).
const segments = value.split('/');
if (segments.some(segment => segment === '.' || segment === '..')) {
throw new Error(
`Value for ${propertyName} must not contain segments that are exactly . or ..`,
);
}
}
}

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

validateUriPath does not decode the path segments before checking for . or ... A user could bypass this check by using percent-encoded characters (e.g., %2e%2e). To ensure robust protection against path traversal, each segment should be decoded using decodeURIComponent before checking.

export function validateUriPath(propertyName: string, value: string): void {
  if (value) {
    const segments = value.split('/');
    const hasTraversal = segments.some(segment => {
      try {
        const decoded = decodeURIComponent(segment);
        return decoded === '.' || decoded === '..';
      } catch {
        return segment === '.' || segment === '..';
      }
    });
    if (hasTraversal) {
      throw new Error(
        `Value for ${propertyName} must not contain segments that are exactly . or ..`,
      );
    }
  }
}

Comment thread core/common/src/util.ts
Comment on lines +1092 to +1115
export function encodeURIPath(uri: string): string {
const parts = uri.split('/');
return parts
.map(part => {
if (part === '') {
return '';
}
if (part.includes(':')) {
const subparts = part.split(':');
return subparts
.map(subpart => {
if (subpart === '') {
return '';
}
validateUriPathSegment('path segment', subpart);
return encodeWithSlashes(subpart);
})
.join(':');
}
validateUriPathSegment('path segment', part);
return encodeWithSlashes(part);
})
.join('/');
}

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

encodeURIPath currently double-encodes any path segments that are already percent-encoded (e.g., %20 becomes %2520). This will break legitimate requests that contain pre-encoded characters (such as spaces or non-ASCII characters in table/dataset IDs). Additionally, if the input is already percent-encoded, the path traversal checks in validateUriPathSegment can be bypassed.

To fix both issues, each segment/subpart should be decoded using decodeURIComponent before validation and encoding.

export function encodeURIPath(uri: string): string {
  const parts = uri.split('/');
  return parts
    .map(part => {
      if (part === '') {
        return '';
      }
      if (part.includes(':')) {
        const subparts = part.split(':');
        return subparts
          .map(subpart => {
            if (subpart === '') {
              return '';
            }
            let decoded = subpart;
            try {
              decoded = decodeURIComponent(subpart);
            } catch {
              // Fallback to raw subpart if decoding fails
            }
            validateUriPathSegment('path segment', decoded);
            return encodeWithSlashes(decoded);
          })
          .join(':');
      }
      let decoded = part;
      try {
        decoded = decodeURIComponent(part);
      } catch {
        // Fallback to raw part if decoding fails
      }
      validateUriPathSegment('path segment', decoded);
      return encodeWithSlashes(decoded);
    })
    .join('/');
}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant