no-insecure-url.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright (c) Microsoft Corporation.
  2. // Licensed under the MIT License.
  3. /**
  4. * @fileoverview Disallows usage of insecure protocols in URL strings
  5. */
  6. "use strict";
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. const DEFAULT_BLOCKLIST = [
  11. /^(ftp|http|telnet|ws):\/\//i
  12. ];
  13. const DEFAULT_EXCEPTIONS = [ // TODO: add more typical false positives such as XML schemas after more testing
  14. /^http:(\/\/|\\u002f\\u002f)schemas\.microsoft\.com(\/\/|\\u002f\\u002f)?.*/i,
  15. /^http:(\/\/|\\u002f\\u002f)schemas\.openxmlformats\.org(\/\/|\\u002f\\u002f)?.*/i,
  16. /^http:(\/|\\u002f){2}localhost(:|\/|\\u002f)*/i,
  17. /^http:(\/\/)www\.w3\.org\/1999\/xhtml/i,
  18. /^http:(\/\/)www\.w3\.org\/2000\/svg/i
  19. ];
  20. const DEFAULT_VARIABLES_EXECEPTIONS = [];
  21. module.exports = {
  22. defaultBlocklist: DEFAULT_BLOCKLIST,
  23. defaultExceptions: DEFAULT_EXCEPTIONS,
  24. defaultVarExecptions: DEFAULT_VARIABLES_EXECEPTIONS,
  25. meta: {
  26. type: "suggestion",
  27. fixable: "code",
  28. schema: [
  29. {
  30. type: "object",
  31. properties: {
  32. blocklist: {
  33. type: "array",
  34. items: {
  35. type: "string"
  36. }
  37. },
  38. exceptions: {
  39. type: "array",
  40. items: {
  41. type: "string"
  42. }
  43. },
  44. varExceptions: {
  45. type: "array",
  46. items: {
  47. type: "string"
  48. }
  49. },
  50. },
  51. additionalProperties: false
  52. }
  53. ],
  54. docs: {
  55. category: "Security",
  56. description: "Insecure protocols such as [HTTP](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol) or [FTP](https://en.wikipedia.org/wiki/File_Transfer_Protocol) should be replaced by their encrypted counterparts ([HTTPS](https://en.wikipedia.org/wiki/HTTPS), [FTPS](https://en.wikipedia.org/wiki/FTPS)) to avoid sending (potentially sensitive) data over untrusted network in plaintext.",
  57. url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/no-insecure-url.md"
  58. },
  59. messages: {
  60. doNotUseInsecureUrl: "Do not use insecure URLs"
  61. }
  62. },
  63. create: function (context) {
  64. const options = context.options[0] || {};
  65. const blocklist = (options.blocklist || DEFAULT_BLOCKLIST).map((pattern) => { return new RegExp(pattern, "i"); });
  66. const exceptions = (options.exceptions || DEFAULT_EXCEPTIONS).map((pattern) => { return new RegExp(pattern, "i"); });
  67. const varExceptions = (options.varExceptions || DEFAULT_VARIABLES_EXECEPTIONS).map((pattern) => { return new RegExp(pattern, "i"); });
  68. function matches(patterns, value) {
  69. return patterns.find((re) => { return re.test(value) }) !== undefined;
  70. }
  71. function shouldFix( varExceptions,context, node) {
  72. let sourceCode = context.getSourceCode();
  73. // check variable for unfixable pattern e.g. `var insecureURL = "http://..."`
  74. let text = node.parent
  75. ? sourceCode.getText(node.parent)
  76. : sourceCode.getText(node);
  77. // if no match, fix the line
  78. return !matches(varExceptions,text);
  79. }
  80. return {
  81. "Literal"(node) {
  82. if (typeof node.value === "string") {
  83. // Add an exception for xmlns attributes
  84. if(node.parent && node.parent.type === "JSXAttribute" && node.parent.name && node.parent.name.name === "xmlns")
  85. {
  86. // Do nothing
  87. }
  88. else if (matches(blocklist, node.value) && !matches(exceptions, node.value) && shouldFix(varExceptions,context, node)) {
  89. context.report({
  90. node: node,
  91. messageId: "doNotUseInsecureUrl",
  92. fix(fixer) {
  93. // Only fix if it contains an http url
  94. if (node.value.toLowerCase().includes("http")) {
  95. let fixedString = node.value.replace(/http:/i, "https:");
  96. //insert an "s" before ":/" to change http:/ to https:/
  97. return fixer.replaceText(node, JSON.stringify(fixedString));
  98. }
  99. },
  100. });
  101. }
  102. }
  103. },
  104. "TemplateElement"(node) {
  105. if (typeof node.value.raw === "string" && typeof node.value.cooked === "string") {
  106. const rawStringText = node.value.raw;
  107. const cookedStringText = node.value.cooked;
  108. if (shouldFix(varExceptions,context, node) && (matches(blocklist, rawStringText) && !matches(exceptions, rawStringText)) ||
  109. (matches(blocklist, cookedStringText) && !matches(exceptions, cookedStringText))) {
  110. context.report({
  111. node: node,
  112. messageId: "doNotUseInsecureUrl",
  113. fix(fixer) {
  114. // Only fix if it contains an http url
  115. if (node.value.raw.toLowerCase().includes("http")) {
  116. let escapedString = JSON.stringify(context.getSourceCode().getText(node));
  117. // delete "" that JSON.stringify created and convert to `` string
  118. escapedString = ``+ escapedString.substring(1, escapedString.length-1);
  119. let fixedString = escapedString.replace(/http:/i, "https:");
  120. //insert an "s" before ":/" to change http:/ to https:/
  121. return fixer.replaceText(node, fixedString);
  122. }
  123. }
  124. });
  125. }
  126. }
  127. },
  128. };
  129. },
  130. };