Copilot commented on code in PR #6687:
URL: https://github.com/apache/hive/pull/6687#discussion_r3754287244


##########
service/src/java/org/apache/hive/service/cli/session/SessionManager.java:
##########
@@ -715,10 +757,59 @@ public void run() {
 
   public HiveSession getSession(SessionHandle sessionHandle) throws 
HiveSQLException {
     HiveSession session = handleToSession.get(sessionHandle);
-    if (session == null) {
+    if (session != null) {
+      if (fetchStrategy == FetchStrategy.ALWAYS && sessionStateStore != null) {
+        syncFromRemoteIfStale(session);
+      }
+      return session;
+    }
+    if (fetchStrategy == FetchStrategy.NEVER) {
       throw new HiveSQLException("Invalid SessionHandle: " + sessionHandle);
     }
-    return session;
+    return recoverSession(sessionHandle);
+  }
+
+  private void syncFromRemoteIfStale(HiveSession session) {
+    try {
+      String handleId = 
session.getSessionHandle().getHandleIdentifier().toString();
+      HiveSessionSnapshot remoteSnapshot = 
sessionStateStore.getSnapshot(handleId);
+      if (remoteSnapshot == null) {
+        return;
+      }
+      if (remoteSnapshot.getLastAccessTime() > session.getLastAccessTime()) {
+        LOG.info("Remote snapshot is newer for session {}, re-hydrating", 
session.getSessionHandle());
+        hydrateSession(session, remoteSnapshot);
+      }
+    } catch (Exception e) {
+      LOG.warn("Failed to sync session from remote store: {}", 
session.getSessionHandle(), e);
+    }
+  }
+
+  private HiveSession recoverSession(SessionHandle sessionHandle) throws 
HiveSQLException {
+    String handleId = sessionHandle.getHandleIdentifier().toString();
+    HiveSessionSnapshot snapshot = sessionStateStore.getSnapshot(handleId);
+    if (snapshot == null) {
+      throw new HiveSQLException("Invalid SessionHandle: " + sessionHandle);
+    }

Review Comment:
   recoverSession() looks up the remote snapshot using 
sessionHandle.getHandleIdentifier().toString(), which only includes the public 
UUID (HandleIdentifier#toString()) and ignores the secret UUID. That means 
anyone who learns a sessionId (public UUID is exposed via REST and logs) can 
craft a SessionHandle with any secret and hijack / impersonate the recovered 
session. The state store key should incorporate both public+secret (or store & 
verify the secret in the snapshot) so the secret remains required to 
recover/access a session.



##########
service/src/java/org/apache/hive/service/cli/session/SessionManager.java:
##########
@@ -577,6 +617,7 @@ public HiveSession createSession(SessionHandle 
sessionHandle, TProtocolVersion p
       throw new HiveSQLException(FAIL_CLOSE_ERROR_MESSAGE);
     }
     registerLlapTargetGaugeIfNeeded(session);
+    saveSessionSnapshot(session);
     LOG.info("Session opened, " + session.getSessionHandle()

Review Comment:
   createSession() unconditionally persists a snapshot immediately after 
opening a session. When called from recoverSession() (or 
CLIService#createSessionWithSessionHandle), this will overwrite the remote 
snapshot with a mostly-empty snapshot before hydrateSession() restores state, 
potentially breaking subsequent recovery attempts and/or racing other HS2 
instances. It’s safer to only persist snapshots for genuinely new sessions 
here, and persist after hydration during recovery.



##########
service/src/java/org/apache/hive/service/cli/session/SessionManager.java:
##########
@@ -154,10 +161,36 @@ public synchronized void init(HiveConf hiveConf) {
       cleanupService = SyncCleanupService.INSTANCE;
     }
     cleanupService.start();
+    initSessionStateStore();
     super.init(hiveConf);
   }
 
-  private void registerOpenSesssionMetrics(Metrics metrics) {
+  private void initSessionStateStore() {
+    String storeClassName = 
hiveConf.getVar(ConfVars.HIVE_SERVER2_SESSION_STATE_STORE_CLASS);
+    String strategyStr = 
hiveConf.getVar(ConfVars.HIVE_SERVER2_SESSION_STATE_STORE_FETCH_STRATEGY);
+    this.fetchStrategy = FetchStrategy.valueOf(strategyStr);
+    if (storeClassName == null || storeClassName.isEmpty()) {
+      LOG.info("Session state store not configured. Persistable sessions 
disabled.");
+      this.sessionStateStore = null;
+      this.fetchStrategy = FetchStrategy.NEVER;
+      return;
+    }
+    try {
+      Class<?> storeClass = Class.forName(storeClassName);
+      this.sessionStateStore = (SessionStateStore) 
storeClass.getDeclaredConstructor().newInstance();
+      this.sessionStateStore.init(hiveConf);
+      LOG.info("Initialized session state store: {}, fetch strategy: {}", 
storeClassName, fetchStrategy);
+    } catch (ClassNotFoundException e) {
+      LOG.warn("Session state store class not found: {}. Persistable sessions 
disabled.", storeClassName);
+      this.sessionStateStore = null;
+      this.fetchStrategy = FetchStrategy.NEVER;
+    } catch (Exception e) {
+      LOG.error("Failed to initialize session state store: {}", 
storeClassName, e);
+      throw new RuntimeException("Failed to initialize session state store", 
e);
+    }
+  }

Review Comment:
   initSessionStateStore() calls FetchStrategy.valueOf(strategyStr) before 
checking whether a store is configured. If the fetch-strategy config is invalid 
(or set to a non-enum value), HS2 will fail to start even though persistable 
sessions are effectively disabled by an empty store class. Consider parsing the 
strategy only after confirming the store is enabled, and defaulting to NEVER on 
invalid values.



##########
service/src/java/org/apache/hive/service/cli/session/PersistableSessionUtils.java:
##########
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.cli.session;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.hive.ql.exec.Utilities;
+import org.apache.hadoop.hive.ql.metadata.Table;
+import org.apache.hadoop.hive.ql.session.SessionState;
+import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hive.service.cli.HiveSQLException;
+import org.apache.hive.service.cli.SessionHandle;
+import org.apache.hive.service.cli.session.store.HiveSessionSnapshot;
+import org.apache.hive.service.cli.session.store.SessionStateStore;
+import org.apache.hive.service.rpc.thrift.TProtocolVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods for the Persistable Sessions feature.
+ * Extracted from SessionManager and HiveSessionImpl to keep
+ * the feature's logic isolated from existing core classes.
+ */
+public final class PersistableSessionUtils {
+
+  public enum FetchStrategy {
+    NEVER,
+    ALWAYS,
+    FETCH_WHEN_MISSING
+  }
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(PersistableSessionUtils.class);
+
+  private static final Pattern STATE_CHANGING_PATTERN = Pattern.compile(
+      "(?i)^\\s*(?:" +
+          "USE\\b|" +
+          "SET\\b|" +
+          "ADD\\s+(?:JAR|FILE)\\b|" +
+          "DELETE\\s+(?:JAR|FILE)\\b|" +
+          "(?:CREATE|DROP)\\s+TEMPORARY\\s+(?:TABLE|FUNCTION)\\b" +
+      ").*");
+
+  private PersistableSessionUtils() {
+  }
+
+  /**
+   * Determines whether a SQL statement changes session state that should
+   * be persisted (database, configs, JARs, temp tables, temp functions).
+   */
+  public static boolean isStateChangingCommand(String statement) {
+    if (statement == null) {
+      return false;
+    }
+    return STATE_CHANGING_PATTERN.matcher(statement).matches();
+  }
+
+  /**
+   * Captures the current session state into a snapshot DTO.
+   */
+  public static HiveSessionSnapshot captureSnapshot(SessionHandle 
sessionHandle,
+      String username, String ipAddress, SessionState sessionState,
+      HiveConf sessionConf, TProtocolVersion protocol,
+      long creationTime, long lastAccessTime) {
+    List<String> jars = new ArrayList<>();
+    String addedJarsStr = Utilities.getResourceFiles(sessionConf, 
SessionState.ResourceType.JAR);
+    if (StringUtils.isNotBlank(addedJarsStr)) {
+      Collections.addAll(jars, addedJarsStr.split(","));
+    }
+
+    Map<String, String> tempTableDefs = new HashMap<>();
+    if (sessionState != null && sessionState.getTempTables() != null) {
+      for (Map.Entry<String, Map<String, Table>> dbEntry :
+          sessionState.getTempTables().entrySet()) {
+        for (Map.Entry<String, Table> tableEntry : 
dbEntry.getValue().entrySet()) {
+          String tableName = tableEntry.getKey();
+          Table table = tableEntry.getValue();
+          String ddl = generateTempTableDDL(tableName, table);
+          if (ddl != null) {
+            tempTableDefs.put(tableName, ddl);
+          }

Review Comment:
   captureSnapshot() iterates SessionState#getTempTables() by database, but 
stores DDLs keyed only by tableName. If multiple DBs have temp tables with the 
same name, entries will clobber; and during hydration there is no way to 
restore temp tables in non-current databases. The snapshot needs to preserve 
the database context per temp table (e.g., dbName -> (tableName -> ddl) or a 
composite key + USE during hydrate).
   
   This issue also appears on line 185 of the same file.



##########
service/src/java/org/apache/hive/service/cli/session/SessionManager.java:
##########
@@ -715,10 +757,59 @@ public void run() {
 
   public HiveSession getSession(SessionHandle sessionHandle) throws 
HiveSQLException {
     HiveSession session = handleToSession.get(sessionHandle);
-    if (session == null) {
+    if (session != null) {
+      if (fetchStrategy == FetchStrategy.ALWAYS && sessionStateStore != null) {
+        syncFromRemoteIfStale(session);
+      }
+      return session;
+    }
+    if (fetchStrategy == FetchStrategy.NEVER) {
       throw new HiveSQLException("Invalid SessionHandle: " + sessionHandle);
     }
-    return session;
+    return recoverSession(sessionHandle);
+  }
+
+  private void syncFromRemoteIfStale(HiveSession session) {
+    try {
+      String handleId = 
session.getSessionHandle().getHandleIdentifier().toString();
+      HiveSessionSnapshot remoteSnapshot = 
sessionStateStore.getSnapshot(handleId);
+      if (remoteSnapshot == null) {
+        return;
+      }
+      if (remoteSnapshot.getLastAccessTime() > session.getLastAccessTime()) {
+        LOG.info("Remote snapshot is newer for session {}, re-hydrating", 
session.getSessionHandle());
+        hydrateSession(session, remoteSnapshot);
+      }
+    } catch (Exception e) {
+      LOG.warn("Failed to sync session from remote store: {}", 
session.getSessionHandle(), e);
+    }
+  }
+
+  private HiveSession recoverSession(SessionHandle sessionHandle) throws 
HiveSQLException {
+    String handleId = sessionHandle.getHandleIdentifier().toString();
+    HiveSessionSnapshot snapshot = sessionStateStore.getSnapshot(handleId);
+    if (snapshot == null) {
+      throw new HiveSQLException("Invalid SessionHandle: " + sessionHandle);
+    }
+    LOG.info("Recovering session from state store: {}", sessionHandle);
+    TProtocolVersion protocol = 
TProtocolVersion.findByValue(snapshot.getProtocolVersion());
+    if (protocol == null) {
+      protocol = TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V1;
+    }
+    SessionHandle recoveredHandle = new SessionHandle(
+        sessionHandle.getHandleIdentifier(), protocol);
+    boolean withImpersonation = 
hiveConf.getBoolVar(ConfVars.HIVE_SERVER2_ENABLE_DOAS)
+        && snapshot.getUsername() != null;
+    HiveSession recovered = createSession(recoveredHandle, protocol,
+        snapshot.getUsername(), null, snapshot.getIpAddress(),
+        null, withImpersonation, null);
+    hydrateSession(recovered, snapshot);
+    LOG.info("Successfully recovered session: {}", sessionHandle);
+    return recovered;

Review Comment:
   After recoverSession() hydrates the recovered session, it never persists an 
updated snapshot. Combined with the unconditional save during createSession(), 
the store may end up with an incomplete snapshot (e.g., missing restored 
configs/current DB/JARs). Persisting once after hydration ensures the store 
reflects the recovered state and refreshes TTL.



##########
service/src/java/org/apache/hive/service/cli/session/HiveSessionImpl.java:
##########
@@ -523,24 +525,32 @@ public HiveConf getSessionConf() throws HiveSQLException {
 
   @Override
   public OperationHandle executeStatement(String statement, Map<String, 
String> confOverlay) throws HiveSQLException {
-    return executeStatementInternal(statement, confOverlay, false, 0);
+    OperationHandle handle = executeStatementInternal(statement, confOverlay, 
false, 0);
+    notifyIfStateChanging(statement);
+    return handle;
   }
 
   @Override
   public OperationHandle executeStatement(String statement, Map<String, 
String> confOverlay,
       long queryTimeout) throws HiveSQLException {
-    return executeStatementInternal(statement, confOverlay, false, 
queryTimeout);
+    OperationHandle handle = executeStatementInternal(statement, confOverlay, 
false, queryTimeout);
+    notifyIfStateChanging(statement);
+    return handle;
   }
 
   @Override
   public OperationHandle executeStatementAsync(String statement, Map<String, 
String> confOverlay) throws HiveSQLException {
-    return executeStatementInternal(statement, confOverlay, true, 0);
+    OperationHandle handle = executeStatementInternal(statement, confOverlay, 
true, 0);
+    notifyIfStateChanging(statement);
+    return handle;

Review Comment:
   notifyIfStateChanging(statement) is invoked immediately after 
executeStatementInternal() returns, even for async operations. For SQLOperation 
with runAsync=true, executeStatementInternal returns after submitting 
background work, so the snapshot can be saved before the state-changing command 
(e.g., CREATE/DROP TEMPORARY TABLE) actually completes, resulting in 
stale/incorrect persisted state. Until there is an async-completion hook to 
snapshot after success, it’s safer to avoid snapshotting from 
executeStatementAsync methods.



##########
service/src/java/org/apache/hive/service/cli/session/PersistableSessionUtils.java:
##########
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hive.service.cli.session;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hive.conf.HiveConf;
+import org.apache.hadoop.hive.ql.exec.Utilities;
+import org.apache.hadoop.hive.ql.metadata.Table;
+import org.apache.hadoop.hive.ql.session.SessionState;
+import org.apache.hadoop.hive.metastore.api.FieldSchema;
+import org.apache.hive.service.cli.HiveSQLException;
+import org.apache.hive.service.cli.SessionHandle;
+import org.apache.hive.service.cli.session.store.HiveSessionSnapshot;
+import org.apache.hive.service.cli.session.store.SessionStateStore;
+import org.apache.hive.service.rpc.thrift.TProtocolVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility methods for the Persistable Sessions feature.
+ * Extracted from SessionManager and HiveSessionImpl to keep
+ * the feature's logic isolated from existing core classes.
+ */
+public final class PersistableSessionUtils {
+
+  public enum FetchStrategy {
+    NEVER,
+    ALWAYS,
+    FETCH_WHEN_MISSING
+  }
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(PersistableSessionUtils.class);
+
+  private static final Pattern STATE_CHANGING_PATTERN = Pattern.compile(
+      "(?i)^\\s*(?:" +
+          "USE\\b|" +
+          "SET\\b|" +
+          "ADD\\s+(?:JAR|FILE)\\b|" +
+          "DELETE\\s+(?:JAR|FILE)\\b|" +
+          "(?:CREATE|DROP)\\s+TEMPORARY\\s+(?:TABLE|FUNCTION)\\b" +
+      ").*");
+
+  private PersistableSessionUtils() {
+  }
+
+  /**
+   * Determines whether a SQL statement changes session state that should
+   * be persisted (database, configs, JARs, temp tables, temp functions).
+   */
+  public static boolean isStateChangingCommand(String statement) {
+    if (statement == null) {
+      return false;
+    }
+    return STATE_CHANGING_PATTERN.matcher(statement).matches();
+  }
+
+  /**
+   * Captures the current session state into a snapshot DTO.
+   */
+  public static HiveSessionSnapshot captureSnapshot(SessionHandle 
sessionHandle,
+      String username, String ipAddress, SessionState sessionState,
+      HiveConf sessionConf, TProtocolVersion protocol,
+      long creationTime, long lastAccessTime) {
+    List<String> jars = new ArrayList<>();
+    String addedJarsStr = Utilities.getResourceFiles(sessionConf, 
SessionState.ResourceType.JAR);
+    if (StringUtils.isNotBlank(addedJarsStr)) {
+      Collections.addAll(jars, addedJarsStr.split(","));
+    }
+
+    Map<String, String> tempTableDefs = new HashMap<>();
+    if (sessionState != null && sessionState.getTempTables() != null) {
+      for (Map.Entry<String, Map<String, Table>> dbEntry :
+          sessionState.getTempTables().entrySet()) {
+        for (Map.Entry<String, Table> tableEntry : 
dbEntry.getValue().entrySet()) {
+          String tableName = tableEntry.getKey();
+          Table table = tableEntry.getValue();
+          String ddl = generateTempTableDDL(tableName, table);
+          if (ddl != null) {
+            tempTableDefs.put(tableName, ddl);
+          }
+        }
+      }
+    }
+
+    return HiveSessionSnapshot.builder()
+        .sessionHandleId(sessionHandle.getHandleIdentifier().toString())
+        .username(username)
+        .ipAddress(ipAddress)
+        .currentDatabase(sessionState != null ? 
sessionState.getCurrentDatabase() : null)
+        .overriddenConfigurations(sessionState != null
+            ? new HashMap<>(sessionState.getOverriddenConfigurations()) : null)
+        .addedJars(jars)
+        .tempTableDefinitions(tempTableDefs)
+        .protocolVersion(protocol.getValue())
+        .creationTime(creationTime)
+        .lastAccessTime(lastAccessTime)
+        .build();
+  }
+
+  /**
+   * Generates the CREATE TEMPORARY TABLE DDL for a temp table,
+   * including LOCATION so data can be recovered on shared storage.
+   */
+  public static String generateTempTableDDL(String tableName, Table table) {
+    try {
+      StringBuilder sb = new StringBuilder("CREATE TEMPORARY TABLE ");
+      sb.append(tableName).append(" (");
+      List<FieldSchema> cols = table.getCols();
+      for (int i = 0; i < cols.size(); i++) {
+        if (i > 0) {
+          sb.append(", ");
+        }
+        sb.append(cols.get(i).getName()).append(" 
").append(cols.get(i).getType());
+      }
+      sb.append(")");
+      if (table.getSerializationLib() != null) {
+        sb.append(" ROW FORMAT SERDE 
'").append(table.getSerializationLib()).append("'");
+      }
+      if (table.getStorageHandler() != null) {
+        sb.append(" STORED BY 
'").append(table.getStorageHandler().getClass().getName()).append("'");
+      } else if (table.getInputFormatClass() != null) {
+        sb.append(" STORED AS INPUTFORMAT 
'").append(table.getInputFormatClass().getName()).append("'");
+        if (table.getOutputFormatClass() != null) {
+          sb.append(" OUTPUTFORMAT 
'").append(table.getOutputFormatClass().getName()).append("'");
+        }
+      }
+      if (table.getDataLocation() != null) {
+        sb.append(" LOCATION '").append(table.getDataLocation()).append("'");
+      }
+      return sb.toString();
+    } catch (Exception e) {
+      LOG.warn("Failed to generate DDL for temp table: {}", tableName, e);
+      return null;
+    }
+  }
+
+  /**
+   * Hydrates a recovered session from a snapshot: restores database, configs,
+   * JARs, and temp tables.
+   */
+  public static void hydrateSession(HiveSession session, HiveSessionSnapshot 
snapshot)
+      throws HiveSQLException {
+    try {
+      SessionState sessionState = session.getSessionState();
+      if (snapshot.getCurrentDatabase() != null) {
+        sessionState.setCurrentDatabase(snapshot.getCurrentDatabase());
+      }
+      if (snapshot.getOverriddenConfigurations() != null) {
+        for (Map.Entry<String, String> entry : 
snapshot.getOverriddenConfigurations().entrySet()) {
+          session.getHiveConf().set(entry.getKey(), entry.getValue());
+          sessionState.getOverriddenConfigurations().put(entry.getKey(), 
entry.getValue());
+        }
+      }
+      if (snapshot.getAddedJars() != null) {
+        for (String jar : snapshot.getAddedJars()) {
+          sessionState.add_resource(SessionState.ResourceType.JAR, jar);
+        }
+      }
+      if (snapshot.getTempTableDefinitions() != null) {
+        for (Map.Entry<String, String> entry : 
snapshot.getTempTableDefinitions().entrySet()) {
+          try {
+            session.executeStatement(entry.getValue(), null);
+          } catch (Exception e) {
+            LOG.warn("Failed to restore temporary table {}: {}", 
entry.getKey(), e.getMessage());
+          }
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Failed to hydrate session: {}", session.getSessionHandle(), 
e);
+      throw new HiveSQLException("Failed to hydrate recovered session", e);
+    }
+  }
+
+  /**
+   * Unwraps a HiveSession proxy to get the underlying HiveSessionImpl.
+   * Returns null if the session cannot be unwrapped.
+   */
+  public static HiveSessionImpl unwrapSession(HiveSession session) {
+    if (session instanceof HiveSessionImpl) {
+      return (HiveSessionImpl) session;
+    }
+    if (Proxy.isProxyClass(session.getClass())) {
+      InvocationHandler handler = Proxy.getInvocationHandler(session);
+      if (handler instanceof HiveSessionProxy) {
+        HiveSession base = ((HiveSessionProxy) handler).getBaseSession();
+        if (base instanceof HiveSessionImpl) {
+          return (HiveSessionImpl) base;
+        }
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Saves the session snapshot to the state store.
+   */
+  public static void saveSnapshot(SessionStateStore store, HiveSession 
session) {
+    if (store == null) {
+      return;
+    }
+    try {
+      HiveSessionImpl impl = unwrapSession(session);
+      if (impl == null) {
+        return;
+      }
+      HiveSessionSnapshot snapshot = impl.captureSnapshot();
+      
store.saveSnapshot(session.getSessionHandle().getHandleIdentifier().toString(), 
snapshot);
+    } catch (Exception e) {

Review Comment:
   saveSnapshot() uses 
session.getSessionHandle().getHandleIdentifier().toString() as the store key, 
which only includes the public UUID and drops the secret UUID. This weakens 
session-handle secrecy and enables session hijacking via recovery if a 
sessionId becomes known. The store key should incorporate both public+secret 
(or store/verify the secret separately) so that recovery requires the full 
handle.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to