Repository: oppia/oppia

Issue:
Add npm audit to linter to catch security issues with js packages.
Currently it is possible to accidentally install vulnerable javascript packages, we should prevent that by implementing `npm audit` into our linter to catch these issues. See the last example in the npm audit [docs](https://docs.npmjs.com/cli/audit#examples) for how to grab those errors.
Add npm audit to linter to catch security issues with js packages.
Currently it is possible to accidentally install vulnerable javascript packages, we should prevent that by implementing `npm audit` into our linter to catch these issues. See the last example in the npm audit [docs](https://docs.npmjs.com/cli/audit#examples) for how to grab those errors.


Discussion:
I think that we should also check libraries that we include by different means than the package.json and if they're vulnerable then prioritize the upgrades with @NishealJ 
I do see one package that is not installed via `package.json`, is it possible to convert it so that it is installed via `package.json`? I ask since `npm audit` does not appear to work for packages not installed via those means. 
I think that we should also check libraries that we include by different means than the package.json and if they're vulnerable then prioritize the upgrades with @NishealJ 
I do see one package that is not installed via `package.json`, is it possible to convert it so that it is installed via `package.json`? I ask since `npm audit` does not appear to work for packages not installed via those means. 

Resolving patch:
diff --git a/scripts/pre_commit_linter.py b/scripts/pre_commit_linter.py
--- a/scripts/pre_commit_linter.py
+++ b/scripts/pre_commit_linter.py
@@ -2489,6 +2489,58 @@ def _check_constants_declaration(self):
 
         return summary_messages
 
+    def _check_npm_packages(self):
+        """Checks to ensure that npm packages are not identified as vulnerable
+        by npm audit.
+        """
+        parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
+
+        npm_path = os.path.join(
+            parent_dir, 'oppia_tools', 'node-10.15.3', 'bin', 'npm')
+        if self.verbose_mode_enabled:
+            print 'Starting npm package vulnerability check...'
+            print '----------------------------------------'
+        print ''
+        if not self.verbose_mode_enabled:
+            print 'Checking npm packages.'
+
+        summary_messages = []
+        failed = False
+
+        proc_args = [npm_path, 'audit', '--parseable']
+        with _redirect_stdout(_TARGET_STDOUT):
+            proc = subprocess.Popen(
+                proc_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+            linter_stdout, _ = proc.communicate()
+
+            errors = linter_stdout.lstrip().rstrip()
+            if errors:
+                failed = True
+                error_list = errors.split('\n')
+                for line in error_list:
+                    line_tokens = line.split('\t')
+                    print ('%s package is vulnerable. Please run "%s" to '
+                           'update.' %
+                           (line_tokens[1], line_tokens[3]))
+
+            if failed:
+                print '(%s errors found)' % len(error_list)
+                summary_message = (
+                    '%s  npm package vulnerability check failed' % (
+                        _MESSAGE_TYPE_FAILED))
+            else:
+                summary_message = (
+                    '%s  npm package vulnerability check passed' % (
+                        _MESSAGE_TYPE_SUCCESS))
+            summary_messages.append(summary_message)
+
+            print ''
+            print summary_message
+            print ''
+
+        return summary_messages
+
 
     def perform_all_lint_checks(self):
         """Perform all the lint checks and returns the messages returned by all
@@ -2519,6 +2571,7 @@ def perform_all_lint_checks(self):
         pattern_messages = self._check_bad_patterns()
         codeowner_messages = self._check_codeowner_file()
         constants_messages = self._check_constants_declaration()
+        npm_package_messages = self._check_npm_packages()
         all_messages = (
             extra_js_files_messages + js_and_ts_component_messages +
             directive_scope_messages + sorted_dependencies_messages +
@@ -2526,7 +2579,7 @@ def perform_all_lint_checks(self):
             mandatory_patterns_messages + docstring_messages +
             comment_messages + html_tag_and_attribute_messages +
             html_linter_messages + linter_messages + pattern_messages +
-            codeowner_messages + constants_messages)
+            codeowner_messages + constants_messages + npm_package_messages)
         return all_messages
 
 


Test patch: