iframe-missing-sandbox.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * @fileoverview TBD
  3. */
  4. 'use strict';
  5. const docsUrl = require('../util/docsUrl');
  6. const isCreateElement = require('../util/isCreateElement');
  7. const report = require('../util/report');
  8. const messages = {
  9. attributeMissing: 'An iframe element is missing a sandbox attribute',
  10. invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"',
  11. invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid',
  12. };
  13. const ALLOWED_VALUES = [
  14. // From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
  15. '',
  16. 'allow-downloads-without-user-activation',
  17. 'allow-downloads',
  18. 'allow-forms',
  19. 'allow-modals',
  20. 'allow-orientation-lock',
  21. 'allow-pointer-lock',
  22. 'allow-popups',
  23. 'allow-popups-to-escape-sandbox',
  24. 'allow-presentation',
  25. 'allow-same-origin',
  26. 'allow-scripts',
  27. 'allow-storage-access-by-user-activation',
  28. 'allow-top-navigation',
  29. 'allow-top-navigation-by-user-activation',
  30. ];
  31. function validateSandboxAttribute(context, node, attribute) {
  32. if (typeof attribute !== 'string') {
  33. // Only string literals are supported for now
  34. return;
  35. }
  36. const values = attribute.split(' ');
  37. let allowScripts = false;
  38. let allowSameOrigin = false;
  39. values.forEach((attributeValue) => {
  40. const trimmedAttributeValue = attributeValue.trim();
  41. if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) {
  42. report(context, messages.invalidValue, 'invalidValue', {
  43. node,
  44. data: {
  45. value: trimmedAttributeValue,
  46. },
  47. });
  48. }
  49. if (trimmedAttributeValue === 'allow-scripts') {
  50. allowScripts = true;
  51. }
  52. if (trimmedAttributeValue === 'allow-same-origin') {
  53. allowSameOrigin = true;
  54. }
  55. });
  56. if (allowScripts && allowSameOrigin) {
  57. report(context, messages.invalidCombination, 'invalidCombination', {
  58. node,
  59. });
  60. }
  61. }
  62. function checkAttributes(context, node) {
  63. let sandboxAttributeFound = false;
  64. node.attributes.forEach((attribute) => {
  65. if (attribute.type === 'JSXAttribute'
  66. && attribute.name
  67. && attribute.name.type === 'JSXIdentifier'
  68. && attribute.name.name === 'sandbox'
  69. ) {
  70. sandboxAttributeFound = true;
  71. if (
  72. attribute.value
  73. && attribute.value.type === 'Literal'
  74. && attribute.value.value
  75. ) {
  76. validateSandboxAttribute(context, node, attribute.value.value);
  77. }
  78. }
  79. });
  80. if (!sandboxAttributeFound) {
  81. report(context, messages.attributeMissing, 'attributeMissing', {
  82. node,
  83. });
  84. }
  85. }
  86. function checkProps(context, node) {
  87. let sandboxAttributeFound = false;
  88. if (node.arguments.length > 1) {
  89. const props = node.arguments[1];
  90. const sandboxProp = props.properties && props.properties.find((x) => x.type === 'Property' && x.key.name === 'sandbox');
  91. if (sandboxProp) {
  92. sandboxAttributeFound = true;
  93. if (sandboxProp.value && sandboxProp.value.type === 'Literal' && sandboxProp.value.value) {
  94. validateSandboxAttribute(context, node, sandboxProp.value.value);
  95. }
  96. }
  97. }
  98. if (!sandboxAttributeFound) {
  99. report(context, messages.attributeMissing, 'attributeMissing', {
  100. node,
  101. });
  102. }
  103. }
  104. module.exports = {
  105. meta: {
  106. docs: {
  107. description: 'Enforce sandbox attribute on iframe elements',
  108. category: 'Best Practices',
  109. recommended: false,
  110. url: docsUrl('iframe-missing-sandbox'),
  111. },
  112. schema: [],
  113. messages,
  114. },
  115. create(context) {
  116. return {
  117. 'JSXOpeningElement[name.name="iframe"]'(node) {
  118. checkAttributes(context, node);
  119. },
  120. CallExpression(node) {
  121. if (isCreateElement(node, context) && node.arguments && node.arguments.length > 0) {
  122. const tag = node.arguments[0];
  123. if (tag.type === 'Literal' && tag.value === 'iframe') {
  124. checkProps(context, node);
  125. }
  126. }
  127. },
  128. };
  129. },
  130. };