VYPR
Critical severityNVD Advisory· Published Dec 14, 2023· Updated May 22, 2025

External Control of File Name or Path in h2oai/h2o-3

CVE-2023-6569

Description

External Control of File Name or Path in h2oai/h2o-3

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

H2O-3 is vulnerable to path traversal due to missing input validation, allowing attackers to read or write arbitrary files on the server.

CVE-2023-6569 describes an external control of file name or path vulnerability in H2O-3, an open-source machine learning platform. The root cause is insufficient validation of user-supplied file paths when reading or writing files, allowing attackers to specify arbitrary paths beyond intended directories.

Exploitation does not require authentication; an attacker can send crafted requests to H2O-3 endpoints that handle file operations, such as model import/export or data loading. By manipulating path arguments, they can traverse the filesystem to access or modify files outside the intended scope [2].

The impact is severe: attackers can read sensitive files (e.g., configuration files, credentials) or overwrite critical system files, potentially leading to remote code execution or data compromise. The vulnerability affects all versions prior to the fix.

Mitigation is available in commit e8884f5, which introduces a file_deny_glob configuration option to block access to sensitive system paths like /bin/*, /etc/*, and /proc/*. Users should update to the latest patched version or apply the workaround by restricting file access at the network or filesystem level [2].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
h2oPyPI
< 3.46.0.13.46.0.1

Affected products

2
  • ghsa-coords
    Range: < 3.46.0.1
  • h2oai/h2oai/h2o-3v5
    Range: unspecified

Patches

1
e8884f5187eb

GH-15972: Add Option Filtering File System For Reading and Writing (#16057)

https://github.com/h2oai/h2o-3krasinskiFeb 29, 2024via ghsa
8 files changed · +78 6
  • h2o-core/src/main/java/water/exceptions/H2OFileAccessDeniedException.java+16 0 added
    @@ -0,0 +1,16 @@
    +package water.exceptions;
    +
    +/**
    + * Exception thrown when a file matches file_deny_glob
    + */
    +public class H2OFileAccessDeniedException extends H2OAbstractRuntimeException {
    +
    +  public H2OFileAccessDeniedException(String message, String dev_message) {
    +    super(message, dev_message);
    +  }
    +
    +  public H2OFileAccessDeniedException(String message) {
    +    super(message, message);
    +  }
    +
    +}
    
  • h2o-core/src/main/java/water/fvec/Frame.java+7 1 modified
    @@ -5,6 +5,7 @@
     import water.*;
     import water.api.FramesHandler;
     import water.api.schemas3.KeyV3;
    +import water.exceptions.H2OFileAccessDeniedException;
     import water.exceptions.H2OIllegalArgumentException;
     import water.parser.BinaryFormatExporter;
     import water.parser.BufferedString;
    @@ -14,7 +15,6 @@
     import java.io.IOException;
     import java.io.InputStream;
     import java.util.*;
    -import java.util.regex.Matcher;
     import java.util.regex.Pattern;
     
     /** A collection of named {@link Vec}s, essentially an R-like Distributed Data Frame.
    @@ -1590,6 +1590,9 @@ public static Job export(Frame fr, String path, String frameName, boolean overwr
                                String compression, CSVStreamParams csvParms) {
         boolean forceSingle = nParts == 1;
         // Validate input
    +    if (H2O.getPM().isFileAccessDenied(path)) {
    +      throw new H2OFileAccessDeniedException("File " + path + " access denied");
    +    }
         if (forceSingle) {
           boolean fileExists = H2O.getPM().exists(path);
           if (overwrite && fileExists) {
    @@ -1613,6 +1616,9 @@ public static Job export(Frame fr, String path, String frameName, boolean overwr
     
       public static Job exportParquet(Frame fr, String path, boolean overwrite, String compression, boolean writeChecksum) {
         // Validate input
    +    if (H2O.getPM().isFileAccessDenied(path)) {
    +      throw new H2OFileAccessDeniedException("File " + path + " access denied");
    +    }
         if (! H2O.getPM().isEmptyDirectoryAllNodes(path)) {
           throw new H2OIllegalArgumentException(path, "exportFrame", "Cannot use path " + path +
                   " to store part files! The target needs to be either an existing empty directory or not exist yet.");
    
  • h2o-core/src/main/java/water/H2O.java+16 1 modified
    @@ -30,6 +30,8 @@
     import java.lang.reflect.InvocationTargetException;
     import java.lang.reflect.Method;
     import java.net.*;
    +import java.nio.file.FileSystems;
    +import java.nio.file.PathMatcher;
     import java.util.*;
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.atomic.AtomicInteger;
    @@ -323,6 +325,9 @@ public static void printHelp() {
         public String context_path = "";
     
         public KeyValueArg[] extra_headers = new KeyValueArg[0];
    +
    +    public PathMatcher file_deny_glob = FileSystems.getDefault().getPathMatcher("glob:{/bin/*,/etc/*,/var/*,/usr/*,/proc/*,**/.**}");
    +
       }
     
       public static class KeyValueArg {
    @@ -843,7 +848,17 @@ else if(s.matches(("client_disconnect_timeout"))){
             i = s.incrementAndCheck(i, args);
             String value = args[i];
             trgt.extra_headers = ArrayUtils.append(trgt.extra_headers, new KeyValueArg(key, value));
    -      } else if(s.matches("embedded")) {
    +      } else if (s.matches("file_deny_glob")) {
    +        i = s.incrementAndCheck(i, args);
    +        String key = args[i];
    +        try {
    +          trgt.file_deny_glob = FileSystems.getDefault().getPathMatcher("glob:" + key);
    +        }
    +        catch (Exception e) {
    +          throw new IllegalArgumentException("Error parsing file_deny_glob parameter");
    +        }
    +      }
    +      else if(s.matches("embedded")) {
             trgt.embedded = true;
           } else {
             parseFailed("Unknown argument (" + s + ")");
    
  • h2o-core/src/main/java/water/persist/Persist.java+1 0 modified
    @@ -302,4 +302,5 @@ public boolean delete(String path) {
       public boolean canHandle(String path) {
         throw new RuntimeException("Not implemented");
       }
    +  
     }
    
  • h2o-core/src/main/java/water/persist/PersistManager.java+16 0 modified
    @@ -16,6 +16,7 @@
     import java.net.HttpURLConnection;
     import java.net.URI;
     import java.net.URL;
    +import java.nio.file.Path;
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.List;
    @@ -841,6 +842,21 @@ public Persist getPersistForURI(URI uri) {
         }
       }
     
    +  /**
    +   * Returns true when path matches file_deny_glob input argument
    +   *
    +   * @param path path to a file
    +   * @return boolean
    +   */
    +  public boolean isFileAccessDenied(String path) {
    +    File f = new File(FileUtils.getURI(path));
    +    return isFileAccessDenied(f.toPath());
    +  }
    +
    +  public boolean isFileAccessDenied(Path path) {
    +    return H2O.ARGS.file_deny_glob.matches(path.normalize());
    +  }
    +
       /**
        * Finds all entries in the list that matches the regex
        * @param prefix The substring to extract before pattern matching
    
  • h2o-core/src/main/java/water/persist/PersistNFS.java+4 0 modified
    @@ -6,6 +6,7 @@
     import java.util.ArrayList;
     
     import water.*;
    +import water.exceptions.H2OFileAccessDeniedException;
     import water.exceptions.H2ONotFoundArgumentException;
     import water.fvec.NFSFileVec;
     import water.util.FileIntegrityChecker;
    @@ -124,6 +125,9 @@ public ArrayList<String> calcTypeaheadMatches(String filter, int limit) {
       @Override
       public void importFiles(String path, String pattern, ArrayList<String> files, ArrayList<String> keys, ArrayList<String> fails, ArrayList<String> dels) {
         File f = new File(FileUtils.getURI(path));
    +    if (H2O.ARGS.file_deny_glob.matches(f.toPath().normalize())) {
    +      throw new H2OFileAccessDeniedException("File " + path + " access denied");
    +    }
         if( !f.exists() ) throw new H2ONotFoundArgumentException("File " + path + " does not exist");
         FileIntegrityChecker.check(f).syncDirectory(files,keys,fails,dels);
       }
    
  • h2o-core/src/test/java/water/persist/PersistManagerTest.java+17 4 modified
    @@ -5,6 +5,7 @@
     import org.junit.Test;
     import org.junit.rules.TemporaryFolder;
     import water.*;
    +import water.exceptions.H2OFileAccessDeniedException;
     import water.fvec.*;
     
     import java.io.*;
    @@ -33,15 +34,15 @@ public void calcTypeaheadMatches_emptyPath() {
             List<String> matches = persistManager.calcTypeaheadMatches("", 100);
             assertNotNull(matches);
             assertEquals(0, matches.size());
    -        
    +
             // Path with spaces (testing trim is being done)
             matches = persistManager.calcTypeaheadMatches("   ", 100);
             assertNotNull(matches);
             assertEquals(0, matches.size());
         }
     
         @Test
    -    public void createReturnsBufferedOutputStreamForFiles() throws IOException  {
    +    public void createReturnsBufferedOutputStreamForFiles() throws IOException {
             File target = new File(tmp.getRoot(), "target.txt");
             try (OutputStream os = persistManager.create(target.getAbsolutePath(), false)) {
                 assertTrue(os instanceof BufferedOutputStream);
    @@ -150,7 +151,7 @@ public void testCreateHexPath() throws IOException {
             }
         }
     
    -    
    +
         @Test
         public void testDeltaLakeMetadataFilter() {
             PersistManager.DeltaLakeMetadataFilter filter = new PersistManager.DeltaLakeMetadataFilter();
    @@ -169,5 +170,17 @@ public void testDeltaLakeMetadataFilter() {
                     "dbfs:///_delta_log/b/fileB.parquet"
             ), result);
         }
    -    
    +
    +    @Test
    +    public void testImportFileMatchingDenyList() {
    +        Exception actual = null;
    +        try {
    +            persistManager.importFiles("/etc/hosts", null, new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>(), new ArrayList<String>());
    +        } catch (Exception e) {
    +            actual = e;
    +        }
    +        assertNotNull(actual);
    +        assertTrue(actual instanceof H2OFileAccessDeniedException);
    +    }
    +
     }
    
  • h2o-docs/src/product/starting-h2o.rst+1 0 modified
    @@ -283,6 +283,7 @@ H2O Options
     - ``-log_dir <fileSystemPath>``: Specify the directory where H2O writes logs to disk. (This usually has a good default that you need not change.
     - ``-log_level <TRACE,DEBUG,INFO,WARN,ERRR,FATAL>``: Specify to write messages at this logging level, or above. The default is INFO.
     - ``-flow_dir <server-side or HDFS directory>``: Specify a directory for saved flows. The default is ``/Users/h2o-<H2OUserName>/h2oflows`` (where ``<H2OUserName>`` is your user name).
    +- ``-file_deny_glob <GLOB pattern>``: Specify `glob <https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob>`_. pattern to deny access to certain files. The default is ``{/bin/*,/etc/*,/var/*,/usr/*,/proc/*,**/.**}``.
     - ``-nthreads <#ofThreads>``: Specify the maximum number of threads in the low-priority batch work queue (where ``<#ofThreads>`` is the number of threads). 
     - ``-client``: Launch H2O node in client mode. This is used mostly for running Sparkling Water.
     - ``-notify_local <fileSystemPath>``: Specifies a file to write to when the node is up. The file system path contains a single line with the IP and port of the embedded web server. For example, 192.168.1.100:54321. 
    

Vulnerability mechanics

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

References

4

News mentions

0

No linked articles in our index yet.