Skip to content

feat(storage): Implement robust path validation and structured skip reporting#7546

Open
thiyaguk09 wants to merge 2 commits intogoogleapis:mainfrom
thiyaguk09:fix/download-directory-path-traversal
Open

feat(storage): Implement robust path validation and structured skip reporting#7546
thiyaguk09 wants to merge 2 commits intogoogleapis:mainfrom
thiyaguk09:fix/download-directory-path-traversal

Conversation

@thiyaguk09
Copy link
Contributor

  • Adds protection against path traversal (../) using normalized path resolution.
  • Prevents Windows-style drive letter injection while allowing GCS timestamps.
  • Implements directory jail logic to ensure absolute-style paths are relative to destination.
  • Preserves backward compatibility by returning an augmented DownloadResponse array.
  • Automates recursive directory creation for validated nested files.
  • Adds a comprehensive 13-scenario test suite for edge-case parity.

- Adds protection against path traversal (../) using normalized path
resolution.
- Prevents Windows-style drive letter injection while allowing GCS
timestamps.
- Implements directory jail logic to ensure absolute-style paths are
relative to destination.
- Preserves backward compatibility by returning an augmented
DownloadResponse array.
- Automates recursive directory creation for validated nested files.
- Adds comprehensive 13-scenario test suite for edge-case parity.
@product-auto-label product-auto-label bot added the api: storage Issues related to the Cloud Storage API. label Mar 10, 2026
@thiyaguk09 thiyaguk09 marked this pull request as ready for review March 10, 2026 08:26
@thiyaguk09 thiyaguk09 requested a review from a team as a code owner March 10, 2026 08:26
@quirogas quirogas added the priority: p2 Moderately-important priority. Fix may not be included in next release. label Mar 10, 2026
let files: File[] = [];

const baseDestination = path.resolve(
options.prefix || options.passthroughOptions?.destination || '.'

Choose a reason for hiding this comment

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

Should we prioritize options.passthroughOptions?.destination first before options.prefix?


return Promise.all(promises);
const results = await Promise.all(promises);
return [...skippedFiles, ...results];

Choose a reason for hiding this comment

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

Maintain the input order. Results should be placed in the array at the same index as their corresponding input file. Otherwise this could potentially be a breaking change.

Comment on lines +616 to +619
dest = path.join(
options.prefix || '',
passThroughOptionsCopy.destination || '',
file.name,
normalizedGcsName

Choose a reason for hiding this comment

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

Why are we combining prefix and destination here? , previously in line 580, you prioritised prefix over baseDestinations.

I think you need to only use baseDestination everywhere and combine it with the object name and do path traversal validations.

if (options.stripPrefix) {
passThroughOptionsCopy.destination = file.name.replace(regex, '');

const resolvedPath = path.resolve(baseDestination, dest);

Choose a reason for hiding this comment

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

If dest is an absolute path, path.Resolve will return dest as is. But that is not the behavior we want. If object name is /etc/passwd and if the user given options.passthroughOptions?.destination is /usr/downloads, the final path should be /usr/downloads/etc/passwd. Can you check if this is happening in this scenario?

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

Labels

api: storage Issues related to the Cloud Storage API. priority: p2 Moderately-important priority. Fix may not be included in next release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants