{"record":{"id":"0dc61b7fa69b6482","repo":"pentaho/pentaho-kettle","slug":"unable-to-use-a-symbolic-link","errorCode":null,"errorMessage":"unable to use a symbolic link: ","messagePattern":"unable to use a symbolic link: ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"plugins/pentaho-googledrive-vfs/core/src/main/java/org/pentaho/googledrive/vfs/util/CustomDataStoreFactory.java","lineNumber":48,"sourceCode":"import java.io.Serializable;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.Set;\nimport java.util.concurrent.locks.Lock;\nimport java.util.concurrent.locks.ReentrantLock;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Iterator;\n\npublic class CustomDataStoreFactory extends AbstractDataStoreFactory {\n\n  private final File dataDirectory;\n\n  public CustomDataStoreFactory( File dataDirectory ) throws IOException {\n    dataDirectory = dataDirectory.getCanonicalFile();\n    this.dataDirectory = dataDirectory;\n    if ( IOUtils.isSymbolicLink( dataDirectory ) ) {\n      throw new IOException( \"unable to use a symbolic link: \" + dataDirectory );\n    } else if ( !dataDirectory.exists() && !dataDirectory.mkdirs() ) {\n      throw new IOException( \"unable to create directory: \" + dataDirectory );\n    }\n  }\n\n  protected <V extends Serializable> DataStore<V> createDataStore( String id ) throws IOException {\n    return new CustomDataStore( this, this.dataDirectory, id );\n  }\n\n  static class CustomDataStore<V extends Serializable> extends AbstractDataStore<V> {\n    private File dataFile = null;\n    private File dataDirectory = null;\n    private HashMap<String, byte[]> keyValueMap = Maps.newHashMap();\n    private final Lock lock = new ReentrantLock();\n\n    CustomDataStore( CustomDataStoreFactory dataStore, File dataDirectory, String id ) throws IOException {\n      super( dataStore, id );\n      this.dataDirectory = dataDirectory;","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/pentaho-googledrive-vfs/core/src/main/java/org/pentaho/googledrive/vfs/util/CustomDataStoreFactory.java#L30-L66","documentation":"CustomDataStoreFactory's constructor rejects a data directory that is a symbolic link. It canonicalizes the path first, then throws IOException 'unable to use a symbolic link: <dir>' if the canonical path is still a symlink, as a security measure against symlink attacks on the local Google Drive token/data cache.","triggerScenarios":"Constructing new CustomDataStoreFactory(dataDirectory) where dataDirectory (after getCanonicalFile()) is a symlink — e.g. pointing the cache dir at a symlinked folder, or /tmp-style dirs replaced by symlinks.","commonSituations":"Dev setups where the data directory is a symlink to another disk; container images where /tmp or the app dir is a symlink; hardening-sensitive environments where this check is intentional.","solutions":["Point the data directory at a real directory, not a symlink.","Bind-mount or copy the target directory to a physical path and use that path.","If a symlink is unavoidable, replace it (rm the link, mkdir a real dir) and move data over."],"exampleFix":"// before\nFile dir = new File(\"/var/lib/app/drive-link\"); // symlink\nnew CustomDataStoreFactory(dir);\n// after\nFile dir = new File(\"/var/lib/app/drive\").getCanonicalFile(); // real directory\nnew CustomDataStoreFactory(dir);","handlingStrategy":"validation","validationCode":"File canonical = dir.getCanonicalFile();\nif (java.nio.file.Files.isSymbolicLink(canonical.toPath())) {\n  throw new IllegalStateException(\"data directory must not be a symlink: \" + canonical);\n}","typeGuard":null,"tryCatchPattern":"try {\n  new CustomDataStoreFactory(dataDirectory);\n} catch (IOException e) {\n  if (e.getMessage().startsWith(\"unable to use a symbolic link\")) {\n    // reconfigure to a real directory\n  } else { throw e; }\n}","preventionTips":["Configure real (non-symlink) directories for token/data caches.","Avoid symlink-based /tmp layouts in containers; use bind mounts of real directories.","Call getCanonicalFile() yourself early to fail fast with a clearer message."],"tags":["google-drive","vfs","symlink","security","io"],"backgroundTag":"path-traversal-blocked","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}