Security
WebdriverIO has the security aspect in mind when providing solutions. Below are some ways to better secure your test.
Best Practice
- Never hardcode sensitive data that can harm your organization if exposed in clear text.
- Use a mechanism (such as a vault) to securely store keys and passwords and retrieve them when starting your end-to-end tests.
- Verify that no sensitive data is exposed in Logs and by the cloud provider, such as authentication tokens in Network Logs.
Even for test data, it is essential to ask whether, in the wrong hands, a malicious person could retrieve information or use those resources with malicious intent.
Masking Sensitive Data
If you are using sensitive data during your test, it is essential to ensure that they are not visible to everyone, such as in logs. Also, when using a cloud provider, private keys are often involved. This information must be masked from logs, reporters, and other touchpoints. The following provides some masking solutions to run tests without exposing those values.
WebDriverIO
Mask Commands' Text Value
The commands addValue
and setValue
support a boolean mask value to mask in logs, as well as reporters. Moreover, other tools, such as performance tools and third-party tools, will also receive the mask version, enhancing security.
For example, if you are using a real production user and need to enter a password that you want to mask, then it is now possible with the following:
async enterPassword(userPassword) {
const passwordInputElement = $('Password');
// Get focus
await passwordInputElement.click();
await passwordInputElement.setValue(userPassword, { mask: true });
}
The above will hide the text value from WDIO logs as the following:
Logs example:
INFO webdriver: DATA { text: "**MASKED**" }
Reporters, such as Allure reporters, and third-party tools like Percy from BrowserStack will also handle the masked version. Paired with the proper Appium version, the Appium Logs will also be exempt from your sensitive data.
Limitations:
- In Appium, additional plugins could leak even though we ask to mask the information.
- Cloud providers could use a proxy for HTTP logging, which bypasses the mask mechanism put in place.
- The
getValue
command is not supported. Moreover, if used on the same element, it can expose the value intended to be masked when usingaddValue
orsetValue
.
Minimum required version:
- WDIO v9.15.0
- Appium v3.0.0
Mask in WDIO Logs
Using the maskingPatterns
configuration, we can mask sensitive information from WDIO logs. However, Appium logs are not covered.
For example, if you are using a Cloud provider and use the info level, then most certainly you will "leak" the user's key as shown below:
INFO @wdio/local-runner: Start worker 0-0 with arg: ./wdio.conf.ts --user=cloud_user --key=myCloudSecretExposedKey --spec myTest.test.ts
To counter that we can pass the the regular expression '--key=([^ ]*)'
and now in the logs you will see
INFO @wdio/local-runner: Start worker 0-0 with arg: ./wdio.conf.ts --user=cloud_user --key=**MASKED** --spec myTest.test.ts
You can achieve the above by providing the regular expression to the maskingPatterns
field of the configuration.
- For multiple regular expressions, use a single string but with a comma-separated value.
- For more details on masking patterns, see the Masking Patterns section in the WDIO Logger README.
export const config: WebdriverIO.Config = {
specs: [...],
capabilities: [{...}],
services: ['lighthouse'],
/**
* test configurations
*/
logLevel: 'info',
maskingPatterns: '/--key=([^ ]*)/',
framework: 'mocha',
outputDir: __dirname,
reporters: ['spec'],
mochaOpts: {
ui: 'bdd',
timeout: 60000
}
}
Minimum required version:
- WDIO v9.15.0
Disable WDIO Loggers
Another way to block the logging of sensitive data is to lower or silence the log level or disable the logger. It can achieved as follow:
import logger from '@wdio/logger';
/**
* Set the logger level of the WDIO logger to 'silent' before *running a promise, which helps hide sensitive information in the logs.
*/
export const withSilentLogger = async <T>(promise: () => Promise<T>): Promise<T> => {
const webdriverLogLevel = driver.options.logLevel ?? 'error';
try {
logger.setLevel('webdriver', 'silent');
return await promise();
} finally {
logger.setLevel('webdriver', webdriverLogLevel);
}
};
Thirds Party Solutions
Appium
Appium offers its masking solution; see Log filter
- It can be tricky to use their solution. One way if possible is to pass a token in your string like
@mask@
and use it as a regular expression - In some Appium versions, the values are also logged with each character comma-separated, so we need to be careful.
- Unfortunately, BrowserStack does not support this solution, but it is still useful locally
Using the @mask@
example previously mentioned, we can use the following JSON file named appiumMaskLogFilters.json
[
{
"pattern": "@mask@(.*)",
"flags": "s",
"replacer": "**MASKED**"
},
{
"pattern": "\\[(\\\"@\\\",\\\"m\\\",\\\"a\\\",\\\"s\\\",\\\"k\\\",\\\"@\\\",\\S+)\\]",
"flags": "s",
"replacer": "[*,*,M,A,S,K,E,D,*,*]"
}
]
Then pass the JSON file name to the logFilters
field into the appium service config:
import { AppiumServerArguments, AppiumServiceConfig } from '@wdio/appium-service';
import { ServiceEntry } from '@wdio/types/build/Services';
const appium = [
'appium',
{
args: {
log: './logs/appium.log',
logFilters: './appiumMaskLogFilters.json',
} satisfies AppiumServerArguments,
} satisfies AppiumServiceConfig,
] satisfies ServiceEntry;
BrowserStack
BrowserStack also offer some level of masking to hide some data; see hide sensitive data
- Unfortunately, the solution is all-or-nothing, so all text values of provided commands will be masked.