VYPR
High severity8.8NVD Advisory· Published Mar 13, 2026· Updated Apr 14, 2026

CVE-2026-4092

CVE-2026-4092

Description

Path Traversal in Clasp impacting versions < 3.2.0 allows a remote attacker to perform remote code execution via a malicious Google Apps Script project containing specially crafted filenames with directory traversal sequences.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@google/claspnpm
< 3.2.03.2.0

Affected products

1
  • cpe:2.3:a:google:clasp:*:*:*:*:*:*:*:*
    Range: <3.2.0

Patches

1
ba6bd666fe74

fix: prevent path traversal in remote file synchronization (#1109)

https://github.com/google/claspⳕⲛτⲉⲅⲥⲉⳏτⲟⲅ 🕵🏻Jan 31, 2026via ghsa
1 file changed · +43 11
  • src/core/files.ts+43 11 modified
    @@ -55,6 +55,23 @@ function parentDirs(file: string) {
       }
       return parentDirs;
     }
    +function isInside(parentPath: string, childPath: string): boolean {
    +
    +  const relative = path.relative(parentPath, childPath);
    +
    +  return (
    +
    +    relative !== '' &&
    +
    +    !relative.startsWith('..') &&
    +
    +    !path.isAbsolute(relative)
    +
    +  );
    +
    +}
    +
    +
     
     async function getLocalFiles(rootDir: string, ignorePatterns: string[], recursive: boolean) {
       debug('Collecting files in %s', rootDir);
    @@ -190,12 +207,8 @@ export class Files {
         this.options = options;
       }
     
    -  /**
    +/**
        * Fetches the content of a script project from Google Drive.
    -   * @param {number} [versionNumber] - Optional version number to fetch.
    -   * If not specified, the latest version (HEAD) is fetched.
    -   * @returns {Promise<ProjectFile[]>} A promise that resolves to an array of project files.
    -   * @throws {Error} If there's an API error or authentication/configuration issues.
        */
       async fetchRemote(versionNumber?: number): Promise<ProjectFile[]> {
         debug('Fetching remote files, version %s', versionNumber ?? 'HEAD');
    @@ -207,29 +220,47 @@ export class Files {
         const scriptId = this.options.project.scriptId;
         const script = google.script({version: 'v1', auth: credentials});
         const fileExtensionMap = this.options.files.fileExtensions;
    +
         try {
           const requestOptions = {scriptId, versionNumber};
           debug('Fetching script content, request %o', requestOptions);
    +
           const response = await script.projects.getContent(requestOptions);
           const files = response.data.files ?? [];
    +
    +      // 1. Establish the security boundary (the "jail")
    +      const absoluteContentDir = path.resolve(contentDir);
    +
           return files.map(f => {
             const ext = getFileExtension(f.type, fileExtensionMap);
    -        const localPath = path.relative(process.cwd(), path.resolve(contentDir, `${f.name}${ext}`));
     
    -        const file = {
    -          localPath: localPath,
    +        // 2. Resolve the absolute path for the remote file
    +        const resolvedPath = path.resolve(contentDir, `${f.name}${ext}`);
    +
    +        // 3. SECURITY CHECK: Ensure path is strictly inside contentDir
    +        // This prevents traversal (../../) and prefix attacks (/foo/bar vs /foo/bar1)
    +        if (!isInside(absoluteContentDir, resolvedPath)) {
    +          throw new Error(
    +            `Security Error: Remote file name "${f.name}" attempts to write outside the project directory.`
    +          );
    +        }
    +
    +        const localPath = path.relative(process.cwd(), resolvedPath);
    +
    +        const file: ProjectFile = {
    +          localPath,
               remotePath: f.name ?? undefined,
               source: f.source ?? undefined,
               type: f.type ?? undefined,
             };
    +
             debug('Fetched file %O', file);
             return file;
           });
    -    } catch (error) {
    -      handleApiError(error);
    +    } catch (err) {
    +      throw handleApiError(err as GaxiosError);
         }
       }
    -
       /**
        * Collects all local files in the project's content directory, respecting ignore patterns.
        * It reads the content of each file and determines its type.
    @@ -570,3 +601,4 @@ function extractSyntaxError(error: GaxiosError, files: ProjectFile[]) {
       snippet = preLines + '\n' + errLine + '\n' + postLines;
       return {message, snippet}; // Return the formatted message and snippet.
     }
    +
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.