Repository: appium/appium
Pull request #21264 (2025-05-14 16:39:33)

chore: Bump latest changes from master


Patch:
diff --git a/package-lock.json b/package-lock.json
index 8172725c3..38fe41b1b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18456,12 +18456,12 @@
       }
     },
     "packages/appium": {
-      "version": "3.0.0-beta.0",
+      "version": "3.0.0-beta.1",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
-        "@appium/base-driver": "^10.0.0-beta.0",
-        "@appium/base-plugin": "^3.0.0-beta.0",
+        "@appium/base-driver": "^10.0.0-beta.2",
+        "@appium/base-plugin": "^3.0.0-beta.2",
         "@appium/docutils": "^1.1.0",
         "@appium/logger": "^1.7.0",
         "@appium/schema": "^0.8.1",
@@ -18484,7 +18484,7 @@
         "semver": "7.7.1",
         "source-map-support": "0.5.21",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "winston": "3.17.0",
         "wrap-ansi": "7.0.0",
         "ws": "8.18.1",
@@ -18528,9 +18528,9 @@
       "license": "ISC"
     },
     "packages/appium/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -18573,7 +18573,7 @@
     },
     "packages/base-driver": {
       "name": "@appium/base-driver",
-      "version": "10.0.0-beta.1",
+      "version": "10.0.0-beta.2",
       "license": "Apache-2.0",
       "dependencies": {
         "@appium/support": "^6.1.0",
@@ -18594,7 +18594,7 @@
         "path-to-regexp": "8.2.0",
         "serve-favicon": "2.5.0",
         "source-map-support": "0.5.21",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "engines": {
         "node": "^20.9.0 || >=22.11.0",
@@ -18975,9 +18975,9 @@
       }
     },
     "packages/base-driver/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -19002,7 +19002,7 @@
     },
     "packages/base-plugin": {
       "name": "@appium/base-plugin",
-      "version": "3.0.0-beta.1",
+      "version": "3.0.0-beta.2",
       "license": "Apache-2.0",
       "dependencies": {
         "@appium/base-driver": "^10.0.0-beta.0",
@@ -19028,7 +19028,7 @@
         "read-pkg": "5.2.0",
         "source-map-support": "0.5.21",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "yaml": "2.7.1",
         "yargs": "17.7.2",
         "yargs-parser": "21.1.1"
@@ -19122,9 +19122,9 @@
       }
     },
     "packages/docutils/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -19159,7 +19159,7 @@
         "lodash": "4.17.21",
         "sinon": "20.0.0",
         "source-map-support": "0.5.21",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "engines": {
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -19182,9 +19182,9 @@
       }
     },
     "packages/driver-test-support/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -20140,7 +20140,7 @@
         "source-map-support": "0.5.21",
         "supports-color": "8.1.1",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "uuid": "11.1.0",
         "which": "4.0.0",
         "yauzl": "3.2.0"
@@ -20315,9 +20315,9 @@
       }
     },
     "packages/support/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -20408,7 +20408,7 @@
         "@appium/logger": "^1.7.0",
         "@appium/schema": "^0.8.1",
         "@appium/tsconfig": "^0.3.5",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "engines": {
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0",
@@ -20416,9 +20416,9 @@
       }
     },
     "packages/types/node_modules/type-fest": {
-      "version": "4.40.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-      "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw==",
+      "version": "4.40.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+      "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==",
       "license": "(MIT OR CC0-1.0)",
       "engines": {
         "node": ">=16"
@@ -20516,7 +20516,7 @@
         "serve-favicon": "2.5.0",
         "source-map-support": "0.5.21",
         "spdy": "4.0.2",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "dependencies": {
         "accepts": {
@@ -20778,9 +20778,9 @@
           }
         },
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         },
         "type-is": {
           "version": "2.0.0",
@@ -20814,7 +20814,7 @@
         "read-pkg": "5.2.0",
         "source-map-support": "0.5.21",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "yaml": "2.7.1",
         "yargs": "17.7.2",
         "yargs-parser": "21.1.1"
@@ -20863,9 +20863,9 @@
           }
         },
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         },
         "yaml": {
           "version": "2.7.1",
@@ -20886,7 +20886,7 @@
         "lodash": "4.17.21",
         "sinon": "20.0.0",
         "source-map-support": "0.5.21",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "dependencies": {
         "axios": {
@@ -20899,9 +20899,9 @@
           }
         },
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         }
       }
     },
@@ -21537,7 +21537,7 @@
         "source-map-support": "0.5.21",
         "supports-color": "8.1.1",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "uuid": "11.1.0",
         "which": "4.0.0",
         "yauzl": "3.2.0"
@@ -21655,9 +21655,9 @@
           }
         },
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         },
         "uuid": {
           "version": "11.1.0",
@@ -21707,13 +21707,13 @@
         "@appium/logger": "^1.7.0",
         "@appium/schema": "^0.8.1",
         "@appium/tsconfig": "^0.3.5",
-        "type-fest": "4.40.0"
+        "type-fest": "4.40.1"
       },
       "dependencies": {
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         }
       }
     },
@@ -24369,8 +24369,8 @@
     "appium": {
       "version": "file:packages/appium",
       "requires": {
-        "@appium/base-driver": "^10.0.0-beta.0",
-        "@appium/base-plugin": "^3.0.0-beta.0",
+        "@appium/base-driver": "^10.0.0-beta.2",
+        "@appium/base-plugin": "^3.0.0-beta.2",
         "@appium/docutils": "^1.1.0",
         "@appium/logger": "^1.7.0",
         "@appium/schema": "^0.8.1",
@@ -24393,7 +24393,7 @@
         "semver": "7.7.1",
         "source-map-support": "0.5.21",
         "teen_process": "2.3.1",
-        "type-fest": "4.40.0",
+        "type-fest": "4.40.1",
         "winston": "3.17.0",
         "wrap-ansi": "7.0.0",
         "ws": "8.18.1",
@@ -24420,9 +24420,9 @@
           "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
         },
         "type-fest": {
-          "version": "4.40.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.0.tgz",
-          "integrity": "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="
+          "version": "4.40.1",
+          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.40.1.tgz",
+          "integrity": "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="
         },
         "ws": {
           "version": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
diff --git a/packages/appium/docs/en/cli/args.md b/packages/appium/docs/en/cli/args.md
index 8ee5776bc..f41c71c19 100644
--- a/packages/appium/docs/en/cli/args.md
+++ b/packages/appium/docs/en/cli/args.md
@@ -23,14 +23,14 @@ below.
 |--|--|--|--|--|
 |`--address`|IP address to listen on|string|`0.0.0.0`|`-a`|
 |`--allow-cors`|Whether the Appium server should allow web browser connections from any host|boolean|`false`||
-|`--allow-insecure`|Set which insecure features are allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Note that features defined via `--deny-insecure` will be disabled, even if also listed here. If string, a path to a text file containing policy or a comma-delimited list.|array<string>|`[]`||
+|`--allow-insecure`|Set which [insecure features](../guides/security.md) are allowed to run in this server's sessions. Most features are defined on a driver level; see driver documentation for more details. Individual features can be overridden by `--deny-insecure`. Has no effect in combination with `--relaxed-security`.|array<string>|`[]`||
 |`--base-path`|Base path to use as the prefix for all webdriver routes running on the server|string|`""`|`-pa`|
 |`--callback-address`|Callback IP address (default: same as `--address`)|string||`-ca`|
 |`--callback-port`|Callback port (default: same as `--port`) (Value must be between `1` and `65535`)|integer|`4723`|`-cp`|
 |`--config`|Path to an [Appium configuration JSON file](../guides/config.md)|string|||
 |`--debug-log-spacing`|Add exaggerated spacing in logs to help with visual inspection|boolean|`false`||
 |`--default-capabilities`|Set the default desired capabilities, which will be set on each session unless overridden by received capabilities. If a string, a path to a JSON file containing the capabilities, or raw JSON.|object||`-dc`|
-|`--deny-insecure`|Set which insecure features are not allowed to run in this server's sessions. Features are defined on a driver level; see documentation for more details. Features listed here will not be enabled even if also listed in `--allow-insecure`, and even if `--relaxed-security` is enabled. If string, a path to a text file containing policy or a comma-delimited list.|array<string>|`[]`||
+|`--deny-insecure`|Set which [insecure features](../guides/security.md) are not allowed to run in this server's sessions. Most features are defined on a driver level; see driver documentation for more details. Since all insecure features are disabled by default, this argument has no effect without either `--allow-insecure` or `--relaxed-security`, and is applied after both.|array<string>|`[]`||
 |`--driver`|Driver-specific configuration. Keys should correspond to driver package names|object|||
 |`--drivers-import-chunk-size`|The maximum amount of drivers that could be imported in parallel on server startup|number|`3`||
 |`--keep-alive-timeout`|Number of seconds the Appium server should apply as both the keep-alive timeout and the connection timeout for all requests. Setting this to `0` disables the timeout.|integer|`600`|`-ka`|
@@ -48,7 +48,7 @@ below.
 |`--plugin`|Plugin-specific configuration. Keys should correspond to plugin package names|object|||
 |`--plugins-import-chunk-size`|The maximum amount of plugins that could be imported in parallel on server startup|number|`7`||
 |`--port`|Port to listen on (Value must be between `1` and `65535`)|integer|`4723`|`-p`|
-|`--relaxed-security`|Disable additional security checks, so it is possible to use some advanced features, provided by drivers supporting this option. Only enable it if all the clients are in the trusted network and it's not the case if a client could potentially break out of the session sandbox. Specific features can be overridden by using `--deny-insecure`|boolean|`false`||
+|`--relaxed-security`|Allow all [insecure features](../guides/security.md). Only use this if all clients are in a trusted network and could not potentially break out of the session sandbox. Specific features can be overridden by using `--deny-insecure`.|boolean|`false`||
 |`--session-override`|Enables session override (clobbering)|boolean|`false`||
 |`--ssl-cert-path`|Absolute path to the `.cert` file if TLS is used. Must be provided together with `--ssl-key-path`. See the [SSL/TLS/SPDY Support guide](../guides/tls.md) for details|string|||
 |`--ssl-key-path`|Absolute path to the `.key` file if TLS is used. Must be provided together with `--ssl-cert-path`. See the [SSL/TLS/SPDY Support guide](../guides/tls.md) for details|string|||
diff --git a/packages/appium/docs/en/developing/sensitive.md b/packages/appium/docs/en/developing/sensitive.md
new file mode 100644
index 000000000..162ef0b2b
--- /dev/null
+++ b/packages/appium/docs/en/developing/sensitive.md
@@ -0,0 +1,49 @@
+---
+hide:
+  - toc
+
+title: Masking Sensitive Log Data
+---
+
+Since Appium server version 2.18.0 there is a possibility to mask sensitive
+values in logs. The below tutorial explains how to use this feature in third-party
+extensions.
+
+## Why It Might Be Useful
+
+It is the right call to hide sensitive information, like passwords, tokens, etc.
+from server logs, so it does not accidentally leak if these logs end up in wrong hands.
+Appium server already provides a way to manipulate logs records via
+[filtering](../guides/log-filters.md), although it has its own limitations.
+The current approach is more sophisticated though, and requires some fine-tuning
+on the driver/plugin side.
+
+## How To
+
+The assumption is that your extension uses the standard built-in
+[@appium/logger](https://www.npmjs.com/package/@appium/logger).
+In order to get some value in logs replaced by a generic mask it is necessary:
+
+- Change the logging expression to wrap sensitive values and format them, for example:
+
+    ```js
+    this.log.info(`Value: ${value}`);
+    ```
+
+    becomes
+
+    ```js
+    import {logger} from '@appium/support';
+
+    this.log.info('Value: %s', logger.markSensitive(value));
+    ```
+
+    The formatting happens via the standard Node.js's
+    [util.format](https://nodejs.org/api/util.html#utilformatformat-args) API.
+
+- While sending the appropriate server request, where this log line is used and should be masked,
+  add the custom header `X-Appium-Is-Sensitive` with its value set to `1` or `true` (case-insensitive).
+  Without such header the above log value is not going to be masked.
+  This way it is possible to conditionally mask log records depending on which
+  request is being handled by the extension if the log expression is used in the
+  common section.
diff --git a/packages/appium/docs/en/guides/security.md b/packages/appium/docs/en/guides/security.md
index f35098907..ce487f8d0 100644
--- a/packages/appium/docs/en/guides/security.md
+++ b/packages/appium/docs/en/guides/security.md
@@ -27,9 +27,9 @@ Appium when starting it from the command line:
 
 |<div style="width:10em">Parameter</div>|Description|
 |---------------------------------------|-----------|
-|`--relaxed-security`                   |Setting this flag turns on _all_ insecure features (unless blocked by `--deny-insecure`; see below)|
-|`--allow-insecure`                     |Setting this flag to a comma-separated list of feature names or a path to a file containing a feature list (each name on a separate line) will allow _only_ the features listed. For example, `--allow-insecure=adb_shell` will cause _only_ the ADB shell execution feature to be enabled. This is true _unless_ `--relaxed-security` is also used, in which case all features will still be enabled. It makes no sense to combine this flag with `--relaxed-security`.|
-|`--deny-insecure`                      |This flag can likewise be set to a comma-separated list of feature names, or a path to a feature file. Any features listed here will be _disabled_, regardless of whether `--relaxed-security` is set and regardless of whether the names are also listed with `--allow-insecure`.|
+|`--relaxed-security`|Turns on _all_ insecure features (unless blocked by `--deny-insecure`; see below)|
+|`--allow-insecure`|Turns on _only_ specified features. Features can be provided as a comma-separated list of feature names, or in the [Appium Configuration file](./config.md). For example, `--allow-insecure=adb_shell` will cause _only_ the ADB shell execution feature to be enabled. Has no effect when used in combination with `--relaxed-security`.|
+|`--deny-insecure`|Explicitly turns _off_ specified features, overriding `--relaxed-security` and any features specified using `--allow-insecure`. Like `--allow-insecure`, features can be provided as a comma-separated list of feature names, or in the [Appium Configuration file](./config.md).|
 
 ## Insecure Features
 
@@ -44,17 +44,15 @@ might use. Here is an incomplete list of examples from some of Appium's official
 |`record_audio`|Allow recording of host machine audio inputs|XCUITest|
 |`execute_driver_script`| Allows to send a request which has multiple Appium commands.|Execute Driver Plugin|
 
-## Driver-scope security
+Some insecure features operate on the server level, and do not require a driver session:
 
-Since Appium server version 2.12.3 there is a possibility to prefix each feature name with the corresponding automationName where it is going to be applied. For example, if you provide the following features to allow:
-`uiautomator2:adb_shell,xcuitest:get_server_logs,*:record_audio`, then the `adb_shell` would only be allowed
-for the UiAutomator2 driver, `get_server` - for the XCUITest driver and the `record_audio` one - for
-all installed drivers. Feature names provided without explicit automation name prefix are equivalent to
-these having the `*:` prefix. The same feature naming rule applies to the list of denied features.
+|<div style="width:12em">Feature Name</div>|Description|
+|------------|-----------|
+|`session_discovery`|Allows retrieving the list of active server sessions via `GET /appium/sessions`|
 
 ## Examples
 
-To turn on the `get_server_logs` feature for my Appium server, I could start it like this:
+To turn on the `get_server_logs` feature, the Appium server could be started like this:
 
 ```bash
 appium --allow-insecure=get_server_logs
@@ -71,3 +69,29 @@ To allow all features except one:
 ```bash
 appium --relaxed-security --deny-insecure=adb_shell
 ```
+
+## Driver-scope security
+
+Since Appium server version `2.13`, it is possible to apply features on a per-driver basis. This
+can be achieved by prefixing each feature name with the `automationName` of the driver for which
+the feature should be applied. To apply a feature for all drivers, or to apply a server-level
+feature, the wildcard (`*`) prefix should be used.
+
+For example, the server could be started as follows:
+```bash
+appium --allow-insecure=uiautomator2:adb_shell,xcuitest:get_server_logs,*:record_audio
+```
+
+This would result in the following:
+
+* The `adb_shell` feature would be enabled only for the UiAutomator2 driver
+* The `get_server_logs` feature would be enabled only for the XCUITest driver
+* The `record_audio` feature would be enabled for all drivers
+
+Feature provided without an explicit prefix are equivalent to having the wildcard prefix.
+These prefix rules apply to both the `--allow-insecure` and `--deny-insecure` server arguments.
+
+!!! warning
+
+    Starting from Appium 3, the scope prefix is required, and features provided without a scope will
+    raise an error. Note that the behavior of the `--relaxed-security` flag remains unchanged.
diff --git a/packages/appium/docs/mkdocs-en.yml b/packages/appium/docs/mkdocs-en.yml
index 4dc8e10e4..59de92cf9 100644
--- a/packages/appium/docs/mkdocs-en.yml
+++ b/packages/appium/docs/mkdocs-en.yml
@@ -75,6 +75,7 @@ nav:
       - developing/build-plugins.md
       - developing/build-docs.md
       - developing/build-doctor-checks.md
+      - developing/sensitive.md
       - Developer Reference:
           - developing/config-system.md
   - Contributing: contributing.md
diff --git a/packages/appium/lib/appium.js b/packages/appium/lib/appium.js
index 410138417..3ded2ac6a 100644
--- a/packages/appium/lib/appium.js
+++ b/packages/appium/lib/appium.js
@@ -17,16 +17,12 @@ import {
   isW3cCaps,
 } from '@appium/base-driver';
 import AsyncLock from 'async-lock';
-import {
-  parseCapsForInnerDriver,
-  pullSettings,
-  makeNonW3cCapsError,
-  validateFeatures,
-} from './utils';
+import {parseCapsForInnerDriver, pullSettings, makeNonW3cCapsError} from './utils';
 import {util} from '@appium/support';
 import {getDefaultsForExtension} from './schema';
-import {DRIVER_TYPE, BIDI_BASE_PATH} from './constants';
+import {DRIVER_TYPE, BIDI_BASE_PATH, SESSION_DISCOVERY_FEATURE} from './constants';
 import * as bidiCommands from './bidi-commands';
+import * as insecureFeatures from './insecure-features';
 import * as inspectorCommands from './inspector-commands';
 
 const desiredCapabilityConstraints = /** @type {const} */ ({
@@ -190,6 +186,11 @@ class AppiumDriver extends DriverCore {
     });
   }
 
+  /**
+   * Retrieve information about all active sessions
+   * @returns {Promise<import('@appium/types').MultiSessionData[]>}
+   * @deprecated Use {@linkcode getAppiumSessions} instead
+   */
   async getSessions() {
     return _.toPairs(this.sessions).map(([id, driver]) => ({
       id,
@@ -197,9 +198,18 @@ class AppiumDriver extends DriverCore {
     }));
   }
 
+  /**
+   * Retrieve information about all active sessions.
+   * Results are returned only if the `session_discovery` insecure feature is enabled.
+   * @returns {Promise<import('@appium/types').TimestampedMultiSessionData[]>}
+   */
   async getAppiumSessions () {
-    throw new errors.NotImplementedError('Not implemented yet. ' +
-      'Please check https://github.com/appium/appium/issues/20880 for more details.');
+    this.assertFeatureEnabled(SESSION_DISCOVERY_FEATURE);
+    return _.toPairs(this.sessions).map(([id, driver]) => ({
+      id,
+      created: driver.sessionCreationTimestampMs,
+      capabilities: /** @type {import('@appium/types').DriverCaps<any>} */ (driver.caps),
+    }));
   }
 
   printNewSessionAnnouncement(driverName, driverVersion, driverBaseVersion) {
@@ -320,18 +330,7 @@ class AppiumDriver extends DriverCore {
 
       const driverInstance = /** @type {ExternalDriver} */ (new InnerDriver(this.args, true));
 
-      // We want to assign security values directly on the driver. The driver
-      // should not read security values from `this.opts` because those values
-      // could have been set by a malicious user via capabilities, whereas we
-      // want a guarantee the values were set by the appium server admin
-      if (this.args.relaxedSecurityEnabled) {
-        this.log.info(
-          `Applying relaxed security to '${InnerDriver.name}' as per ` +
-            `server command line argument. All insecure features will be ` +
-            `enabled unless explicitly disabled by --deny-insecure`,
-        );
-        driverInstance.relaxedSecurityEnabled = true;
-      }
+      this.configureDriverFeatures(driverInstance, driverName);
 
       // We also want to assign any new Bidi Commands that the driver has specified, including all
       // the standard bidi commands. But add a method existence guard since some old driver class
@@ -340,18 +339,6 @@ class AppiumDriver extends DriverCore {
         driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
       }
 
-      if (!_.isEmpty(this.args.denyInsecure)) {
-        this.log.info('Explicitly preventing use of insecure features:');
-        this.args.denyInsecure.map((a) => this.log.info(`    ${a}`));
-        driverInstance.denyInsecure = validateFeatures(this.args.denyInsecure);
-      }
-
-      if (!_.isEmpty(this.args.allowInsecure)) {
-        this.log.info('Explicitly enabling use of insecure features:');
-        this.args.allowInsecure.map((a) => this.log.info(`    ${a}`));
-        driverInstance.allowInsecure = validateFeatures(this.args.allowInsecure);
-      }
-
       // Likewise, any driver-specific CLI args that were passed in should be assigned directly to
       // the driver so that they cannot be mimicked by a malicious user sending in capabilities
       const cliArgs = this.getCliArgsForDriver(driverName);
@@ -908,6 +895,9 @@ class AppiumDriver extends DriverCore {
   onBidiServerError = bidiCommands.onBidiServerError;
   cleanupBidiSockets = bidiCommands.cleanupBidiSockets;
 
+  configureGlobalFeatures = insecureFeatures.configureGlobalFeatures;
+  configureDriverFeatures = insecureFeatures.configureDriverFeatures;
+
   listCommands = inspectorCommands.listCommands;
   listExtensions = inspectorCommands.listExtensions;
 }
diff --git a/packages/appium/lib/constants.js b/packages/appium/lib/constants.js
index 79f81d1f4..5c65d1d50 100644
--- a/packages/appium/lib/constants.js
+++ b/packages/appium/lib/constants.js
@@ -116,3 +116,8 @@ export const BIDI_BASE_PATH = '/bidi';
  * a bidi socket
  */
 export const BIDI_EVENT_NAME = 'bidiEvent';
+
+/**
+ * The name of the insecure feature that allows retrieving the list of active server sessions
+ */
+export const SESSION_DISCOVERY_FEATURE = 'session_discovery';
diff --git a/packages/appium/lib/insecure-features.ts b/packages/appium/lib/insecure-features.ts
new file mode 100644
index 000000000..3f7986ae0
--- /dev/null
+++ b/packages/appium/lib/insecure-features.ts
@@ -0,0 +1,129 @@
+import _ from 'lodash';
+import logger from './logger';
+
+import type {AppiumDriver} from './appium';
+import type {ExternalDriver} from '@appium/types';
+
+const ALL_DRIVERS_MATCH = '*';
+const FEATURE_NAME_SEPARATOR = ':';
+
+/**
+ * Configures insecure features according to the values in `args.relaxedSecurityEnabled`,
+ * `args.allowInsecure`, and `args.denyInsecure`, and informs the user about any
+ * globally-applied features.
+ * Uses `logger` instead of `this.log` to reduce user confusion.
+ */
+export function configureGlobalFeatures(this: AppiumDriver) {
+  if (this.args.relaxedSecurityEnabled) {
+    logger.info(
+      `Enabling relaxed security. All insecure features will be ` +
+        `enabled unless explicitly disabled by --deny-insecure`,
+    );
+    this.relaxedSecurityEnabled = true;
+  } else if (!_.isEmpty(this.args.allowInsecure)) {
+    this.allowInsecure = validateFeatures(this.args.allowInsecure);
+    const globalAllowedFeatures = filterInsecureFeatures(this.allowInsecure);
+    if (!_.isEmpty(globalAllowedFeatures)) {
+      logger.info('Explicitly enabling insecure features:');
+      globalAllowedFeatures.forEach((a) => logger.info(`    ${a}`));
+    }
+  }
+  if (_.isEmpty(this.args.denyInsecure)) {
+    return;
+  }
+  this.denyInsecure = validateFeatures(this.args.denyInsecure);
+  const globalDeniedFeatures = filterInsecureFeatures(this.denyInsecure);
+  if (_.isEmpty(globalDeniedFeatures)) {
+    return;
+  }
+  logger.info('Explicitly disabling insecure features:');
+  globalDeniedFeatures.forEach((a) => logger.info(`    ${a}`));
+}
+
+/**
+ * If anything in the umbrella driver's insecure feature configuration applies to this driver,
+ * assign it to the driver instance
+ *
+ * @param driver
+ * @param driverName
+ */
+export function configureDriverFeatures(
+  this: AppiumDriver,
+  driver: ExternalDriver,
+  driverName: string,
+) {
+  if (this.relaxedSecurityEnabled) {
+    this.log.info(
+      `Enabling relaxed security for this session as per the server configuration. ` +
+        `All insecure features will be enabled unless explicitly disabled by --deny-insecure`,
+    );
+    driver.relaxedSecurityEnabled = true;
+  }
+  const allowedDriverFeatures = filterInsecureFeatures(this.allowInsecure, driverName);
+  if (!_.isEmpty(allowedDriverFeatures)) {
+    this.log.info('Explicitly enabling insecure features for this session ' +
+      'as per the server configuration:',
+    );
+    allowedDriverFeatures.forEach((a) => this.log.info(`    ${a}`));
+    driver.allowInsecure = allowedDriverFeatures;
+  }
+  const deniedDriverFeatures = filterInsecureFeatures(this.denyInsecure, driverName);
+  if (_.isEmpty(deniedDriverFeatures)) {
+    return;
+  }
+  this.log.info('Explicitly disabling insecure features for this session ' +
+    'as per the server configuration:',
+  );
+  deniedDriverFeatures.forEach((a) => this.log.info(`    ${a}`));
+  driver.denyInsecure = deniedDriverFeatures;
+}
+
+/**
+ * Validates the list of allowed/denied server features
+ *
+ * @param features
+ */
+function validateFeatures(features: string[]): string[] {
+  const validator = (fullName: string) => {
+    const errMsg = `The full feature name must include both the destination automation name or the ` +
+      `'${ALL_DRIVERS_MATCH}' wildcard to apply the feature to all installed drivers, and ` +
+      `the feature name split by a colon. Got '${fullName}' instead`;
+
+    const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
+    if (separatorPos < 0) {
+      throw new Error(errMsg);
+    }
+    const [automationName, featureName] = [
+      fullName.substring(0, separatorPos),
+      fullName.substring(separatorPos + 1)
+    ];
+    if (!automationName || !featureName) {
+      throw new Error(errMsg);
+    }
+    return fullName;
+  };
+  return features.map(validator);
+}
+
+/**
+ * Filters the list of insecure features to only those that are
+ * applicable to the given driver name.
+ * Assumes that all feature names have already been validated
+ *
+ * @param features
+ * @param driverName
+ */
+function filterInsecureFeatures(
+  features: string[],
+  driverName: string = ALL_DRIVERS_MATCH
+): string[] {
+  const filterFn = (fullName: string) => {
+    const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
+    if (separatorPos <= 0) {
+      return false;
+    }
+    const automationName = fullName.substring(0, separatorPos);
+    return [driverName, ALL_DRIVERS_MATCH].includes(automationName);
+  };
+  return features.filter(filterFn);
+}
diff --git a/packages/appium/lib/main.js b/packages/appium/lib/main.js
index 2726690f9..b646d32d7 100755
--- a/packages/appium/lib/main.js
+++ b/packages/appium/lib/main.js
@@ -375,6 +375,9 @@ async function main(args) {
 
   await logStartupInfo(parsedArgs);
 
+  // handle the insecure feature configuration since some features may apply globally
+  appiumDriver.configureGlobalFeatures();
+
   const appiumHomeSourceName = determineAppiumHomeSource(args?.appiumHome);
   logger.debug(`The ${appiumHomeSourceName}: ${appiumHome}`);
 
diff --git a/packages/appium/lib/utils.js b/packages/appium/lib/utils.js
index 1eb7a87b5..93f86f043 100644
--- a/packages/appium/lib/utils.js
+++ b/packages/appium/lib/utils.js
@@ -17,8 +17,6 @@ const STANDARD_CAPS_LOWERCASE = new Set([...STANDARD_CAPS].map((cap) => cap.toLo
 export const V4_BROADCAST_IP = '0.0.0.0';
 export const V6_BROADCAST_IP = '::';
 export const npmPackage = fs.readPackageJsonFrom(__dirname);
-const ALL_DRIVERS_MATCH = '*';
-const FEATURE_NAME_SEPARATOR = ':';
 
 /**
  *
@@ -427,34 +425,6 @@ export function isBroadcastIp(address) {
   return [V4_BROADCAST_IP, V6_BROADCAST_IP, `[${V6_BROADCAST_IP}]`].includes(address);
 }
 
-/**
- * Validates the list of allowed/denied server features
- *
- * @param {string[]} features
- * @returns {string[]}
- */
-export function validateFeatures(features) {
-  const validator = (/** @type {string} */ fullName) => {
-    const errMsg = `The full feature name must include both the destination automation name or the ` +
-      `'${ALL_DRIVERS_MATCH}' wildcard to apply the feature to all installed drivers, and ` +
-      `the feature name split by a colon. Got '${fullName}' instead`;
-
-    const separatorPos = fullName.indexOf(FEATURE_NAME_SEPARATOR);
-    if (separatorPos < 0) {
-      throw new Error(errMsg);
-    }
-    const [automationName, featureName] = [
-      fullName.substring(0, separatorPos),
-      fullName.substring(separatorPos + 1)
-    ];
-    if (!automationName || !featureName) {
-      throw new Error(errMsg);
-    }
-    return fullName;
-  };
-  return features.map(validator);
-}
-
 /**
  * @typedef {import('@appium/types').StringRecord} StringRecord
  * @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
diff --git a/packages/appium/package.json b/packages/appium/package.json
index b0ac47ab6..0b2efaf69 100644
--- a/packages/appium/package.json
+++ b/packages/appium/package.json
@@ -84,7 +84,7 @@
     "semver": "7.7.1",
     "source-map-support": "0.5.21",
     "teen_process": "2.3.1",
-    "type-fest": "4.40.0",
+    "type-fest": "4.40.1",
     "winston": "3.17.0",
     "wrap-ansi": "7.0.0",
     "ws": "8.18.1",
diff --git a/packages/base-driver/lib/basedriver/core.ts b/packages/base-driver/lib/basedriver/core.ts
index c863fca73..f9d68baf9 100644
--- a/packages/base-driver/lib/basedriver/core.ts
+++ b/packages/base-driver/lib/basedriver/core.ts
@@ -41,6 +41,8 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
 
   sessionId: string | null;
 
+  sessionCreationTimestampMs: number;
+
   opts: DriverOpts<C>;
 
   initialOpts: InitialOpts;
@@ -305,7 +307,7 @@ export class DriverCore<const C extends Constraints, Settings extends StringReco
         `Potentially insecure feature '${name}' has not been ` +
           `enabled. If you want to enable this feature and accept ` +
           `the security ramifications, please do so by following ` +
-          `the documented instructions at http://appium.io/docs/en/2.0/guides/security/`,
+          `the documented instructions at http://appium.io/docs/en/latest/guides/security/`,
       );
     }
   }
diff --git a/packages/base-driver/lib/basedriver/driver.ts b/packages/base-driver/lib/basedriver/driver.ts
index 11feec17a..a431ad76e 100644
--- a/packages/base-driver/lib/basedriver/driver.ts
+++ b/packages/base-driver/lib/basedriver/driver.ts
@@ -302,6 +302,7 @@ export class BaseDriver<
     this.validateDesiredCaps(caps);
 
     this.sessionId = util.uuidV4();
+    this.sessionCreationTimestampMs = Date.now();
     this.caps = caps;
     // merge caps onto opts so we don't need to worry about what's where
     this.opts = {..._.cloneDeep(this.initialOpts), ...this.caps};
@@ -345,6 +346,11 @@ export class BaseDriver<
 
     return [this.sessionId, caps] as CreateResult;
   }
+
+  /**
+   * Returns the session id and capabilities for the session
+   * @deprecated Use AppiumDriver.getAppiumSessions instead for getting the session data.
+   */
   async getSessions() {
     const ret: MultiSessionData<C>[] = [];
 
@@ -360,7 +366,7 @@ export class BaseDriver<
 
   /**
    * Returns capabilities for the session and event history (if applicable)
-   * @deprecated Use {@linkcode ISessionCommands.getAppiumSessionCapabilities} instead for getting the capabilities.
+   * @deprecated Use {@linkcode getAppiumSessionCapabilities} instead for getting the capabilities.
    * Use {@linkcode EventCommands.getLogEvents} instead to get the event history.
    */
   async getSession() {
diff --git a/packages/base-driver/lib/protocol/protocol.ts b/packages/base-driver/lib/protocol/protocol.ts
index 4c89e968e..e82dcabb2 100644
--- a/packages/base-driver/lib/protocol/protocol.ts
+++ b/packages/base-driver/lib/protocol/protocol.ts
@@ -317,9 +317,9 @@ function buildHandler(
       if (spec.deprecated && spec.command && !deprecatedCommandsLogged.has(spec.command)) {
         deprecatedCommandsLogged.add(spec.command);
         getLogger(driver, req.params.sessionId).warn(
-          `Command '${spec.command}' has been deprecated and will be removed in a future ` +
-            `version of Appium or your driver/plugin. Please use a different method or contact the ` +
-            `driver/plugin author to add explicit support for the command before it is removed`
+          `The ${method} ${path} endpoint has been deprecated and will be removed in a future ` +
+            `version of Appium or your driver/plugin. Please use a different endpoint or contact the ` +
+            `driver/plugin author to add explicit support for the endpoint before it is removed`
         );
       }
 
diff --git a/packages/base-driver/package.json b/packages/base-driver/package.json
index 11baf12f4..a38a542bc 100644
--- a/packages/base-driver/package.json
+++ b/packages/base-driver/package.json
@@ -62,7 +62,7 @@
     "path-to-regexp": "8.2.0",
     "serve-favicon": "2.5.0",
     "source-map-support": "0.5.21",
-    "type-fest": "4.40.0"
+    "type-fest": "4.40.1"
   },
   "optionalDependencies": {
     "spdy": "4.0.2"
diff --git a/packages/docutils/package.json b/packages/docutils/package.json
index 414c7cc34..d00c883c0 100644
--- a/packages/docutils/package.json
+++ b/packages/docutils/package.json
@@ -58,7 +58,7 @@
     "read-pkg": "5.2.0",
     "source-map-support": "0.5.21",
     "teen_process": "2.3.1",
-    "type-fest": "4.40.0",
+    "type-fest": "4.40.1",
     "yaml": "2.7.1",
     "yargs": "17.7.2",
     "yargs-parser": "21.1.1"
diff --git a/packages/support/lib/plist.js b/packages/support/lib/plist.js
index 4b61f8f04..71b0f6f8c 100644
--- a/packages/support/lib/plist.js
+++ b/packages/support/lib/plist.js
@@ -1,6 +1,6 @@
 import xmlplist from 'plist';
 import bplistCreate from 'bplist-creator';
-import bplistParse from 'bplist-parser';
+import { parseFile, parseBuffer } from 'bplist-parser';
 import {fs} from './fs';
 import log from './logger';
 import _ from 'lodash';
@@ -41,7 +41,7 @@ async function parsePlistFile(plist, mustExist = true, quiet = true) {
   let obj = {};
   let type = 'binary';
   try {
-    obj = await bplistParse.parseFile(plist);
+    obj = await parseFile(plist);
     if (obj.length) {
       obj = obj[0];
     } else {
@@ -108,7 +108,7 @@ function createBinaryPlist(data) {
  * @param {Buffer} data The beffer of a binary plist
  */
 function parseBinaryPlist(data) {
-  return bplistParse.parseBuffer(data);
+  return parseBuffer(data);
 }
 
 function getXmlPlist(data) {
diff --git a/packages/support/package.json b/packages/support/package.json
index 185a31c24..6778215dc 100644
--- a/packages/support/package.json
+++ b/packages/support/package.json
@@ -73,7 +73,7 @@
     "source-map-support": "0.5.21",
     "supports-color": "8.1.1",
     "teen_process": "2.3.1",
-    "type-fest": "4.40.0",
+    "type-fest": "4.40.1",
     "uuid": "11.1.0",
     "which": "4.0.0",
     "yauzl": "3.2.0"
diff --git a/packages/types/lib/driver.ts b/packages/types/lib/driver.ts
index 21c549aa0..cf16ffa2d 100644
--- a/packages/types/lib/driver.ts
+++ b/packages/types/lib/driver.ts
@@ -149,13 +149,29 @@ export interface IExecuteCommands {
   ): Promise<TReturn>;
 }
 
+/**
+ * Data returned by {@linkcode ISessionHandler.getSessions}.
+ *
+ * @typeParam C - The driver's constraints
+ */
 export interface MultiSessionData<C extends Constraints = Constraints> {
   id: string;
   capabilities: DriverCaps<C>;
 }
 
 /**
- * Data returned by {@linkcode ISessionCommands.getSession}.
+ * Data returned by `AppiumDriver.getAppiumSessions`
+ *
+ * @typeParam C - The driver's constraints
+ */
+export interface TimestampedMultiSessionData<C extends Constraints = Constraints> {
+  id: string;
+  created: number; // Unix timestamp in milliseconds
+  capabilities: DriverCaps<C>;
+}
+
+/**
+ * Data returned by {@linkcode ISessionHandler.getSession}.
  *
  * @typeParam C - The driver's constraints
  * @typeParam T - Any extra data the driver stuffs in here
@@ -576,6 +592,7 @@ export interface DriverStatus {
 export interface Core<C extends Constraints, Settings extends StringRecord = StringRecord> {
   shouldValidateCaps: boolean;
   sessionId: string | null;
+  sessionCreationTimestampMs: number;
   opts: DriverOpts<C>;
   initialOpts: InitialOpts;
   protocol?: Protocol;
diff --git a/packages/types/package.json b/packages/types/package.json
index 9161f5d31..97e1694e6 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -41,7 +41,7 @@
     "@appium/logger": "^1.7.0",
     "@appium/schema": "^0.8.1",
     "@appium/tsconfig": "^0.3.5",
-    "type-fest": "4.40.0"
+    "type-fest": "4.40.1"
   },
   "engines": {
     "node": "^14.17.0 || ^16.13.0 || >=18.0.0",


Test patch:
diff --git a/packages/appium/test/unit/appiumdriver.spec.js b/packages/appium/test/unit/appiumdriver.spec.js
index 63c896aae..3226a20e3 100644
--- a/packages/appium/test/unit/appiumdriver.spec.js
+++ b/packages/appium/test/unit/appiumdriver.spec.js
@@ -1,6 +1,6 @@
 // @ts-check
 
-import {PLUGIN_TYPE} from '../../lib/constants';
+import {PLUGIN_TYPE, SESSION_DISCOVERY_FEATURE} from '../../lib/constants';
 import B from 'bluebird';
 import {BaseDriver} from '@appium/base-driver';
 import {FakeDriver} from '@appium/fake-driver';
@@ -14,6 +14,7 @@ import {rewiremock, BASE_CAPS, W3C_CAPS, W3C_PREFIXED_CAPS} from '../helpers';
 import {BasePlugin} from '@appium/base-plugin';
 
 const SESSION_ID = '1';
+const SESSION_DISCOVERY_ENABLED = {allowInsecure: [`*:${SESSION_DISCOVERY_FEATURE}`]};
 
 describe('AppiumDriver', function () {
   /** @type {import('sinon').SinonSandbox} */
@@ -106,13 +107,51 @@ describe('AppiumDriver', function () {
 
       return [appium, mockFakeDriver];
     }
+    describe('configureGlobalFeatures', function () {
+      /** @type {AppiumDriver} */
+      let appium;
+
+      /**
+       * @param {import('@appium/types').DriverOpts<import('../../lib/appium').AppiumDriverConstraints>} cliArgs
+       */
+      function createDriver(cliArgs) {
+        appium = new AppiumDriver(cliArgs);
+        appium.configureGlobalFeatures();
+      };
+      it('should not allow insecure features by default', function () {
+        createDriver({});
+        appium.allowInsecure.should.be.empty;
+        appium.denyInsecure.should.be.empty;
+        appium.relaxedSecurityEnabled.should.be.false;
+      });
+      it('should allow insecure features', function () {
+        createDriver({allowInsecure: ['foo:bar']});
+        appium.allowInsecure.should.eql(['foo:bar']);
+      });
+      it('should deny insecure features', function () {
+        createDriver({denyInsecure: ['foo:baz']});
+        appium.denyInsecure.should.eql(['foo:baz']);
+      });
+      it('should allow relaxed security', function () {
+        createDriver({relaxedSecurityEnabled: true});
+        appium.relaxedSecurityEnabled.should.be.true;
+      });
+      it('should ignore allowed features in combination with relaxed security', function () {
+        createDriver({
+          allowInsecure: ['foo:bar'],
+          relaxedSecurityEnabled: true,
+        });
+        appium.allowInsecure.should.be.empty;
+        appium.relaxedSecurityEnabled.should.be.true;
+      });
+    });
     describe('createSession', function () {
       /** @type {AppiumDriver} */
       let appium;
       /** @type {sinon.SinonMock} */
       let mockFakeDriver;
       beforeEach(function () {
-        [appium, mockFakeDriver] = getDriverAndFakeDriver();
+        [appium, mockFakeDriver] = getDriverAndFakeDriver(SESSION_DISCOVERY_ENABLED);
       });
       afterEach(async function () {
         mockFakeDriver.restore();
@@ -157,6 +196,7 @@ describe('AppiumDriver', function () {
         mockFakeDriver.verify();
       });
       it('should kill all other sessions if sessionOverride is on', async function () {
+        appium.configureGlobalFeatures();
         appium.args.sessionOverride = true;
 
         // mock three sessions that should be removed when the new one is created
@@ -172,7 +212,7 @@ describe('AppiumDriver', function () {
         appium.sessions['xyz-321-abc'] = fakeDrivers[1];
         appium.sessions['123-abc-xyz'] = fakeDrivers[2];
 
-        let sessions = await appium.getSessions();
+        let sessions = await appium.getAppiumSessions();
         sessions.should.have.length(3);
 
         mockFakeDriver
@@ -182,7 +222,7 @@ describe('AppiumDriver', function () {
           .returns([SESSION_ID, removeAppiumPrefixes(W3C_PREFIXED_CAPS)]);
         await appium.createSession(W3C_CAPS, W3C_CAPS, W3C_CAPS);
 
-        sessions = await appium.getSessions();
+        sessions = await appium.getAppiumSessions();
         sessions.should.have.length(1);
 
         for (let mfd of mockFakeDrivers) {
@@ -241,17 +281,18 @@ describe('AppiumDriver', function () {
       let appium;
       let mockFakeDriver;
       beforeEach(function () {
-        [appium, mockFakeDriver] = getDriverAndFakeDriver();
+        [appium, mockFakeDriver] = getDriverAndFakeDriver(SESSION_DISCOVERY_ENABLED);
       });
       afterEach(function () {
         mockFakeDriver.restore();
       });
       it('should remove the session if it is found', async function () {
+        appium.configureGlobalFeatures();
         let [sessionId] = (await appium.createSession(null, null, W3C_CAPS)).value;
-        let sessions = await appium.getSessions();
+        let sessions = await appium.getAppiumSessions();
         sessions.should.have.length(1);
         await appium.deleteSession(sessionId);
-        sessions = await appium.getSessions();
+        sessions = await appium.getAppiumSessions();
         sessions.should.have.length(0);
       });
       it("should call inner driver's deleteSession method", async function () {
@@ -264,11 +305,73 @@ describe('AppiumDriver', function () {
         await mockFakeDriver.object.deleteSession();
       });
     });
-    describe('getSessions', function () {
+    describe('configureDriverFeatures', function () {
+      /** @type {AppiumDriver} */
+      let appium;
+
+      /**
+       *
+       * @param {import('@appium/types').DriverOpts<import('../../lib/appium').AppiumDriverConstraints>} appiumArgs
+       * @returns {Promise<FakeDriver>}
+       */
+      async function getDriverInstance(appiumArgs) {
+        appium = new AppiumDriver(appiumArgs);
+        appium.configureGlobalFeatures();
+        const fakeDriver = new FakeDriver();
+        const mockFakeDriver = sandbox.mock(fakeDriver);
+        const mockedDriverReturnerClass = function Driver() {
+          return fakeDriver;
+        };
+
+        appium.driverConfig = {
+          findMatchingDriver: sandbox.stub().returns({
+            driver: mockedDriverReturnerClass,
+            version: '1.2.3',
+            driverName: 'fake',
+          }),
+        };
+
+        mockFakeDriver
+          .expects('createSession')
+          .once()
+          .withExactArgs(undefined, null, W3C_CAPS, [])
+          .returns([SESSION_ID, removeAppiumPrefixes(W3C_PREFIXED_CAPS)]);
+        await appium.createSession(undefined, null, W3C_CAPS);
+
+        return fakeDriver;
+      }
+      afterEach(async function () {
+        await appium.deleteSession(SESSION_ID);
+      });
+      it(`should not apply any insecure features by default`, async function () {
+        fakeDriver = await getDriverInstance({});
+        fakeDriver.allowInsecure.should.be.empty;
+        fakeDriver.denyInsecure.should.be.empty;
+        fakeDriver.relaxedSecurityEnabled.should.be.false;
+      });
+      it(`should apply relaxed security`, async function () {
+        fakeDriver = await getDriverInstance({relaxedSecurityEnabled: true});;
+        fakeDriver.relaxedSecurityEnabled.should.be.true;
+      });
+      it(`should apply global-scope insecure features`, async function () {
+        fakeDriver = await getDriverInstance({
+          allowInsecure: ['*:foo'],
+          denyInsecure: ['*:bar'],
+        });
+        fakeDriver.allowInsecure.should.eql(['*:foo']);
+        fakeDriver.denyInsecure.should.eql(['*:bar']);
+      });
+      it(`should apply driver-scope insecure features only if the driver name matches`, async function () {
+        fakeDriver = await getDriverInstance({allowInsecure: ['fake:foo', 'real:bar']});
+        fakeDriver.allowInsecure.should.eql(['fake:foo']);
+      });
+    });
+    describe('getAppiumSessions', function () {
       let appium, mockFakeDriver;
       let sessions;
       before(function () {
-        [appium, mockFakeDriver] = getDriverAndFakeDriver();
+        [appium, mockFakeDriver] = getDriverAndFakeDriver(SESSION_DISCOVERY_ENABLED);
+        appium.configureGlobalFeatures();
       });
       afterEach(async function () {
         for (let session of sessions) {
@@ -277,7 +380,7 @@ describe('AppiumDriver', function () {
         mockFakeDriver.restore();
       });
       it('should return an empty array of sessions', async function () {
-        sessions = await appium.getSessions();
+        sessions = await appium.getAppiumSessions();
         sessions.should.be.an('array');
         sessions.should.be.empty;
       });
@@ -299,12 +402,14 @@ describe('AppiumDriver', function () {
           .returns(['fake-session-id-2', removeAppiumPrefixes(caps2.alwaysMatch)]);
         let [session2Id, session2Caps] = (await appium.createSession(null, null, caps2)).value;
 
-        sessions = await appium.getSessions();
+        sessions = await appium.getAppiumSessions();
         sessions.should.be.an('array');
         sessions.should.have.length(2);
         sessions[0].id.should.equal(session1Id);
+        sessions[0].should.have.property('created');
         removeAppiumPrefixes(caps1.alwaysMatch).should.eql(session1Caps);
         sessions[1].id.should.equal(session2Id);
+        sessions[1].should.have.property('created');
         removeAppiumPrefixes(caps2.alwaysMatch).should.eql(session2Caps);
       });
     });
diff --git a/packages/base-driver/test/unit/basedriver/capability.spec.js b/packages/base-driver/test/unit/basedriver/capability.spec.js
index 1ac69b5ef..775cbc9a5 100644
--- a/packages/base-driver/test/unit/basedriver/capability.spec.js
+++ b/packages/base-driver/test/unit/basedriver/capability.spec.js
@@ -158,8 +158,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.true;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.noReset.should.eql(false);
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'noReset' property should exist
+      sessionCaps.capabilities.noReset.should.eql(false);
     });
 
     it('should allow a string "true"', async function () {
@@ -173,8 +174,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.true;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.noReset.should.eql(true);
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'noReset' property should exist
+      sessionCaps.capabilities.noReset.should.eql(true);
     });
 
     it('should allow a string "true" in string capabilities', async function () {
@@ -187,8 +189,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.false;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.language.should.eql('true');
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'language' property should exist
+      sessionCaps.capabilities.language.should.eql('true');
     });
   });
 
@@ -204,8 +207,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.true;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.newCommandTimeout.should.eql(1);
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'newCommandTimeout' property should exist
+      sessionCaps.capabilities.newCommandTimeout.should.eql(1);
     });
 
     it('should allow a string "1.1"', async function () {
@@ -219,8 +223,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.true;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.newCommandTimeout.should.eql(1.1);
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'newCommandTimeout' property should exist
+      sessionCaps.capabilities.newCommandTimeout.should.eql(1.1);
     });
 
     it('should allow a string "1" in string capabilities', async function () {
@@ -233,8 +238,9 @@ describe('Desired Capabilities', function () {
       });
       logWarnSpy.called.should.be.false;
 
-      let sessions = await d.getSessions();
-      sessions[0].capabilities.language.should.eql('1');
+      let sessionCaps = await d.getAppiumSessionCapabilities();
+      // @ts-expect-error the 'language' property should exist
+      sessionCaps.capabilities.language.should.eql('1');
     });
   });
 
diff --git a/packages/driver-test-support/lib/unit-suite.js b/packages/driver-test-support/lib/unit-suite.js
index b0931a204..725009747 100644
--- a/packages/driver-test-support/lib/unit-suite.js
+++ b/packages/driver-test-support/lib/unit-suite.js
@@ -93,26 +93,6 @@ export function driverUnitTestSuite(
       caps.should.equal(await d.getSession());
     });
 
-    it('should return sessions if no session exists', async function () {
-      let sessions = await d.getSessions();
-      sessions.length.should.equal(0);
-    });
-
-    it('should return sessions', async function () {
-      const caps = _.cloneDeep(w3cCaps);
-      await d.createSession(caps);
-      let sessions = await d.getSessions();
-
-      sessions.length.should.equal(1);
-      sessions[0].should.include({
-        id: d.sessionId,
-      });
-      sessions[0].capabilities.should.include({
-        deviceName: 'Commodore 64',
-        platformName: 'Fake',
-      });
-    });
-
     it('should fulfill an unexpected driver quit promise', async function () {
       // make a command that will wait a bit so we can crash while it's running
       sandbox.stub(d, 'getStatus').callsFake(async () => {
diff --git a/packages/driver-test-support/package.json b/packages/driver-test-support/package.json
index d3b61a50d..c8ba78c97 100644
--- a/packages/driver-test-support/package.json
+++ b/packages/driver-test-support/package.json
@@ -50,7 +50,7 @@
     "lodash": "4.17.21",
     "sinon": "20.0.0",
     "source-map-support": "0.5.21",
-    "type-fest": "4.40.0"
+    "type-fest": "4.40.1"
   },
   "peerDependencies": {
     "appium": "^2.0.0-beta.43 || ^3.0.0-beta.0",
diff --git a/packages/universal-xml-plugin/test/unit/plugin.spec.js b/packages/universal-xml-plugin/test/unit/plugin.spec.js
index 1d9aa2eda..f62439ab1 100644
--- a/packages/universal-xml-plugin/test/unit/plugin.spec.js
+++ b/packages/universal-xml-plugin/test/unit/plugin.spec.js
@@ -1,4 +1,4 @@
-import {UniversalXMLPlugin} from '../..';
+import {UniversalXMLPlugin} from '../../lib/plugin';
 import {BaseDriver} from 'appium/driver';
 import {
   XML_IOS,