react-iframe-missing-sandbox.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright (c) Microsoft Corporation.
  2. // Licensed under the MIT License.
  3. /**
  4. * @fileoverview Rule to enforce sandbox attribute on iframe elements
  5. */
  6. "use strict";
  7. // TODO: Follow-up on https://github.com/yannickcr/eslint-plugin-react/issues/2754 and try to merge rule into eslint-plugin-react
  8. //------------------------------------------------------------------------------
  9. // Rule Definition
  10. //------------------------------------------------------------------------------
  11. module.exports = {
  12. meta: {
  13. type: "suggestion",
  14. fixable: "code",
  15. schema: [],
  16. docs: {
  17. category: "Security",
  18. description: "The [sandbox](https://www.w3schools.com/tags/att_iframe_sandbox.asp) attribute enables an extra set of restrictions for the content in the iframe and should always be specified.",
  19. url: "https://github.com/microsoft/eslint-plugin-sdl/blob/master/docs/rules/react-iframe-missing-sandbox.md"
  20. },
  21. messages: {
  22. attributeMissing: 'An iframe element is missing a sandbox attribute',
  23. invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"',
  24. invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid'
  25. }
  26. },
  27. create(context) {
  28. const ALLOWED_VALUES = [
  29. // From https://www.w3schools.com/tags/att_iframe_sandbox.asp
  30. '',
  31. 'allow-forms',
  32. 'allow-modals',
  33. 'allow-orientation-lock',
  34. 'allow-pointer-lock',
  35. 'allow-popups',
  36. 'allow-popups-to-escape-sandbox',
  37. 'allow-presentation',
  38. 'allow-same-origin',
  39. 'allow-scripts',
  40. 'allow-top-navigation',
  41. 'allow-top-navigation-by-user-activation'
  42. ];
  43. function validateSandboxAttribute(node, attribute) {
  44. const values = attribute.value.value.split(' ');
  45. let allowScripts = false;
  46. let allowSameOrigin = false;
  47. values.forEach((attributeValue) => {
  48. const trimmedAttributeValue = attributeValue.trim();
  49. if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) {
  50. context.report({
  51. node,
  52. messageId: 'invalidValue',
  53. data: {
  54. value: trimmedAttributeValue
  55. }
  56. });
  57. }
  58. if (trimmedAttributeValue === 'allow-scripts') {
  59. allowScripts = true;
  60. }
  61. if (trimmedAttributeValue === 'allow-same-origin') {
  62. allowSameOrigin = true;
  63. }
  64. });
  65. if (allowScripts && allowSameOrigin) {
  66. context.report({
  67. node,
  68. messageId: 'invalidCombination'
  69. });
  70. }
  71. }
  72. return {
  73. 'JSXOpeningElement[name.name="iframe"]'(node) {
  74. let sandboxAttributeFound = false;
  75. node.attributes.forEach((attribute) => {
  76. if (attribute.type === 'JSXAttribute'
  77. && attribute.name
  78. && attribute.name.type === 'JSXIdentifier'
  79. && attribute.name.name === 'sandbox'
  80. ) {
  81. sandboxAttributeFound = true;
  82. if (
  83. attribute.value
  84. && attribute.value.type === 'Literal'
  85. && attribute.value.value
  86. ) {
  87. // Only string literals are supported for now
  88. validateSandboxAttribute(node, attribute);
  89. }
  90. }
  91. });
  92. if (!sandboxAttributeFound) {
  93. context.report({
  94. node,
  95. messageId: 'attributeMissing'
  96. });
  97. }
  98. }
  99. };
  100. }
  101. };