Description
Summary:
In com.intuit.karate.core.ScenarioEngine, the current engine instance is stored in a static final ThreadLocal. However, the cleanup mechanism (remove()) is not enforced internally via a try-finally block within the class's lifecycle. It relies entirely on external callers to clean it up.
Root Cause:
If an unhandled exception occurs during execution, or if Karate is invoked via a custom runner/thread-pool that misses the cleanup step, the ScenarioEngine instance remains attached to the thread indefinitely.
Impact (Critical):
-
Memory Leak: ScenarioEngine holds heavy objects like Driver (Selenium/Playwright) and JsEngine (GraalVM). In a long-running environment (e.g., Synthetic Monitoring), this leads to OutOfMemoryError.
-
Data/Credential Leak: The engine holds vars (variables), which often contain secrets, tokens, or user credentials. If the thread is reused (common in web containers or CI runners), a subsequent task could potentially access sensitive data from the previous run.
Location
File: karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java
- Definition (Line 125):
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
- Risky Cleanup (Line 135):
protected static void remove() {
THREAD_LOCAL.remove();
}
// There is no guarantee this is called if the runner crashes.
To Reproduce
-
Run Karate scenarios using a custom thread pool or within a Servlet container.
-
Induce a runtime exception that bypasses the standard runner's cleanup logic.
-
Inspect the Thread Dump: The worker thread will still hold a strong reference to ScenarioEngine (and all its variables/drivers).
Expected Behavior
The lifecycle of ScenarioEngine should be strictly managed.
Recommendation: Wrap the execution logic in a defensive try-finally block that guarantees THREAD_LOCAL.remove() is called, regardless of success or failure.
Description
Summary:
In com.intuit.karate.core.ScenarioEngine, the current engine instance is stored in a static final ThreadLocal. However, the cleanup mechanism (remove()) is not enforced internally via a try-finally block within the class's lifecycle. It relies entirely on external callers to clean it up.
Root Cause:
If an unhandled exception occurs during execution, or if Karate is invoked via a custom runner/thread-pool that misses the cleanup step, the ScenarioEngine instance remains attached to the thread indefinitely.
Impact (Critical):
Memory Leak: ScenarioEngine holds heavy objects like Driver (Selenium/Playwright) and JsEngine (GraalVM). In a long-running environment (e.g., Synthetic Monitoring), this leads to OutOfMemoryError.
Data/Credential Leak: The engine holds vars (variables), which often contain secrets, tokens, or user credentials. If the thread is reused (common in web containers or CI runners), a subsequent task could potentially access sensitive data from the previous run.
Location
File: karate-core/src/main/java/com/intuit/karate/core/ScenarioEngine.java
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
protected static void remove() {
THREAD_LOCAL.remove();
}
// There is no guarantee this is called if the runner crashes.
To Reproduce
Run Karate scenarios using a custom thread pool or within a Servlet container.
Induce a runtime exception that bypasses the standard runner's cleanup logic.
Inspect the Thread Dump: The worker thread will still hold a strong reference to ScenarioEngine (and all its variables/drivers).
Expected Behavior
The lifecycle of ScenarioEngine should be strictly managed.
Recommendation: Wrap the execution logic in a defensive try-finally block that guarantees THREAD_LOCAL.remove() is called, regardless of success or failure.