Harden console functions (#171367)

## Summary

This PR overrides console functions only in production, in order to
sanitize input parameters for any potential calls made to the global
console from Kibana's dependencies.

This initial implementation overrides the `debug`, `error`, `info`,
`log`, `trace`, and `warn` functions, and only sanitizes string inputs.
Future updates may expand this to handle other types, or strings nested
in objects.

The unmodified console methods are now exposed internally in Kibana as
`unsafeConsole`. Where needed for formatting (log appenders, core
logger), calls to the global console have been replaced by
`unsafeConsole`. This PR also adds a new es linting rule to disallow
calls to `unsafeConsole` unless `eslint-disable-next-line
@kbn/eslint/no_unsafe_console` is used.

### Testing
Not sure how we could test this. The overrides are only enabled when
running in a true production environment (e.g. docker) by checking
`process.env.NODE_ENV`.

I was able to manually test by adding additional console output denoting
when the console functions were being overriden or not.

Closes https://github.com/elastic/kibana-team/issues/664
Closes #176340

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jeramy Soucy 2024-02-09 09:13:52 -05:00 committed by GitHub
parent c5819dc09e
commit 2627f48d95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 379 additions and 47 deletions

View file

@ -103,4 +103,8 @@ module.exports = {
}
]
}
```
```
## no_unsafe_console
Disables the usage of kbn-security-hardening/console/unsafeConsole.

View file

@ -17,5 +17,6 @@ module.exports = {
no_trailing_import_slash: require('./rules/no_trailing_import_slash'),
no_constructor_args_in_property_initializers: require('./rules/no_constructor_args_in_property_initializers'),
no_this_in_property_initializers: require('./rules/no_this_in_property_initializers'),
no_unsafe_console: require('./rules/no_unsafe_console'),
},
};

View file

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
const tsEstree = require('@typescript-eslint/typescript-estree');
const esTypes = tsEstree.AST_NODE_TYPES;
/** @typedef {import("eslint").Rule.RuleModule} Rule */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.Node} Node */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.CallExpression} CallExpression */
/** @typedef {import("@typescript-eslint/typescript-estree").TSESTree.CallExpression} VariableDeclarator */
const ERROR_MSG = 'Unexpected unsafeConsole statement.';
/**
* @param {CallExpression} node
*/
const isUnsafeConsoleCall = (node) => {
return (
node.callee.type === esTypes.MemberExpression &&
node.callee.property.type === esTypes.Identifier &&
node.callee.object.name === 'unsafeConsole' &&
node.callee.property.name
);
};
/**
* @param {VariableDeclarator} node
*/
const isUnsafeConsoleObjectPatternDeclarator = (node) => {
return (
node.id.type === esTypes.ObjectPattern &&
node.init &&
node.init.type === esTypes.Identifier &&
node.init.name === 'unsafeConsole'
);
};
/** @type {Rule} */
module.exports = {
meta: {
fixable: 'code',
schema: [],
},
create: (context) => ({
CallExpression(_) {
const node = /** @type {CallExpression} */ (_);
if (isUnsafeConsoleCall(node)) {
context.report({
message: ERROR_MSG,
loc: node.callee.loc,
});
}
},
VariableDeclarator(_) {
const node = /** @type {VariableDeclarator} */ (_);
if (isUnsafeConsoleObjectPatternDeclarator(node)) {
context.report({
message: ERROR_MSG,
loc: node.init.loc,
});
}
},
}),
};

View file

@ -0,0 +1,121 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
const { RuleTester } = require('eslint');
const rule = require('./no_unsafe_console');
const dedent = require('dedent');
const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018,
},
});
ruleTester.run('@kbn/eslint/no_unsafe_console', rule, {
valid: [
{
code: dedent`
unsafeConsole
`,
},
],
invalid: [
{
code: dedent`
unsafeConsole.debug('something to debug')
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.error()
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.info('some info')
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.log('something to log')
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.trace()
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.warn('something to warn')
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
unsafeConsole.anyOtherMethodName()
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
{
code: dedent`
const { debug } = unsafeConsole
debug('something to debug')
`,
errors: [
{
line: 1,
message: 'Unexpected unsafeConsole statement.',
},
],
},
],
});