add --allow-insecure and --deny-insecure server flags
MODIFY
docs/en/writing-running-appium/server-args.md
docs/en/writing-running-appium/server-args.md
@@ -93,3 +93,5 @@ All flags are optional, but some are required in conjunction with certain others
|`--localizable-strings-dir`|en.lproj|[DEPRECATED] - (IOS-only) the relative path of the dir where Localizable.strings file resides |`--localizable-strings-dir en.lproj`|
|`--show-ios-log`|false|[DEPRECATED] - (IOS-only) if set, the iOS system log will be written to the console||
|`--relaxed-security`|false|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 is not the case if a client could potentially break out of the session sandbox.||
+|`--allow-insecure`|[]|Allow a list of features which are considered insecure and must be turned on explicitly by system administrators. Feature names are documented by the relevant server/driver. Should be a comma-separated list, or a path to a filename containing one feature name per line. Features listed in --deny-insecure will override anything listed here.|`--allow-insecure=foo,bar`|
+|`--deny-insecure`|[]|Specify a list of features which will never be allowed to run, even if --relaxed-security is turned on, and even if feature names are listed with --allow-insecure. Should be a comma-separated list, or a path to a filename containing one feature name per line.|`--deny-insecure=foo,bar`|

MODIFY
lib/appium.js
lib/appium.js
@@ -177,6 +177,11 @@ class AppiumDriver extends BaseDriver {
// It is not recommended to access this property directly from the outside
this.pendingDrivers = {};
+ // initialize security modes
+ this.relaxedSecurityEnabled = false;
+ this.allowInsecure = [];
+ this.denyInsecure = [];
+
// allow this to happen in the background, so no `await`
updateBuildInfo();
}
@@ -321,10 +326,30 @@ class AppiumDriver extends BaseDriver {
let runningDriversData, otherPendingDriversData;
const d = new InnerDriver(this.args);
+
+ // 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) {
- log.info(`Applying relaxed security to '${InnerDriver.name}' as per server command line argument`);
+ 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`);
d.relaxedSecurityEnabled = true;
}
+
+ if (!_.isEmpty(this.args.denyInsecure)) {
+ log.info(`Explicitly preventing use of insecure features ` +
+ `${JSON.stringify(this.args.allowInsecure)} for this server`);
+ d.denyInsecure = this.args.denyInsecure;
+ }
+
+ if (!_.isEmpty(this.args.allowInsecure)) {
+ log.info(`Enabling insecure features ${JSON.stringify(this.args.allowInsecure)} ` +
+ `for this server (unless denied by --deny-insecure)`);
+ d.allowInsecure = this.args.allowInsecure;
+ }
+
// This assignment is required for correct web sockets functionality inside the driver
d.server = this.server;
try {

MODIFY
lib/parser.js
lib/parser.js
import fs from 'fs';
+import os from 'os';
import path from 'path';
import _ from 'lodash';
import { ArgumentParser } from 'argparse';
@@ -389,9 +390,37 @@ const args = [
help: '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.',
+ 'break out of the session sandbox. Specific features can be overridden by ' +
+ 'using the --deny-insecure flag',
nargs: 0
}],
+
+ [['--allow-insecure'], {
+ dest: 'allowInsecure',
+ defaultValue: [],
+ type: parseSecurityFeatures,
+ required: false,
+ example: 'execute-webdriver,adb-shell',
+ help: '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. ' +
+ 'This should be either a comma-separated list of feature names, or a path to ' +
+ 'a file where each feature name is on a line. Note that features defined via ' +
+ '--deny-insecure will be disabled, even if also listed here.',
+ }],
+
+ [['--deny-insecure'], {
+ dest: 'denyInsecure',
+ defaultValue: [],
+ type: parseSecurityFeatures,
+ required: false,
+ example: 'execute-webdriver,adb-shell',
+ help: '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. ' +
+ 'This should be either a comma-separated list of feature names, or a path to ' +
+ 'a file where each feature name is on a line. Features listed here will not be ' +
+ 'enabled even if also listed in --allow-insecure, and even if --relaxed-security ' +
+ 'is turned on.',
+ }],
];
const deprecatedArgs = [
@@ -789,6 +818,30 @@ function updateParseArgsForDefaultCapabilities (parser) {
};
}
+function parseSecurityFeatures (features) {
+ const splitter = (splitOn) => {
+ return (str) => {
+ return str.toString().split(splitOn).map(s => s.trim()).filter(Boolean);
+ };
+ };
+ let parsedFeatures;
+ try {
+ parsedFeatures = splitter(',')(features);
+ } catch (err) {
+ throw new Error('Could not parse value of --allow-insecure. Should be a list of strings separated by commas');
+ }
+
+ if (parsedFeatures.length === 1) {
+ // we might have a file which is a list of features
+ try {
+ const fileFeatures = fs.readFileSync(parsedFeatures[0], 'utf8');
+ parsedFeatures = splitter(os.EOL)(fileFeatures);
+ } catch (ign) {}
+ }
+
+ return parsedFeatures;
+}
+
function parseDefaultCaps (caps) {
try {
// use synchronous file access, as `argparse` provides no way of either

ADD
test/fixtures/allow-feat.txt
+feature1
+feature2
+
+feature3
+

ADD
test/fixtures/deny-feat.txt
+
+nofeature1
+nofeature2
+
+nofeature3

MODIFY
test/parser-specs.js
test/parser-specs.js
@@ -5,6 +5,9 @@ import chai from 'chai';
const should = chai.should();
+const ALLOW_FIXTURE = 'test/fixtures/allow-feat.txt';
+const DENY_FIXTURE = 'test/fixtures/deny-feat.txt';
+
describe('Parser', function () {
let p = getParser();
p.debug = true; // throw instead of exit on error; pass as option instead?
@@ -44,4 +47,25 @@ describe('Parser', function () {
let args = p.parseArgs(['--localizable-strings-dir', '/my/dir']);
args.defaultCapabilities.should.eql(defaultCapabilities);
});
+ it('should parse --allow-insecure correctly', function () {
+ p.parseArgs([]).allowInsecure.should.eql([]);
+ p.parseArgs(['--allow-insecure', '']).allowInsecure.should.eql([]);
+ p.parseArgs(['--allow-insecure', 'foo']).allowInsecure.should.eql(['foo']);
+ p.parseArgs(['--allow-insecure', 'foo,bar']).allowInsecure.should.eql(['foo', 'bar']);
+ p.parseArgs(['--allow-insecure', 'foo ,bar']).allowInsecure.should.eql(['foo', 'bar']);
+ });
+ it('should parse --deny-insecure correctly', function () {
+ p.parseArgs([]).denyInsecure.should.eql([]);
+ p.parseArgs(['--deny-insecure', '']).denyInsecure.should.eql([]);
+ p.parseArgs(['--deny-insecure', 'foo']).denyInsecure.should.eql(['foo']);
+ p.parseArgs(['--deny-insecure', 'foo,bar']).denyInsecure.should.eql(['foo', 'bar']);
+ p.parseArgs(['--deny-insecure', 'foo ,bar']).denyInsecure.should.eql(['foo', 'bar']);
+ });
+ it('should parse --allow and --deny insecure from files', function () {
+ const parsed = p.parseArgs([
+ '--allow-insecure', ALLOW_FIXTURE, '--deny-insecure', DENY_FIXTURE
+ ]);
+ parsed.allowInsecure.should.eql(['feature1', 'feature2', 'feature3']);
+ parsed.denyInsecure.should.eql(['nofeature1', 'nofeature2', 'nofeature3']);
+ });
});

add --allow-insecure and --deny-insecure server flags