mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(linter): Implement react/iframe-missing-sandbox (#6383)
https://github.com/oxc-project/oxc/issues/1022 --------- Co-authored-by: Don Isaac <donald.isaac@gmail.com>
This commit is contained in:
parent
a9544ae3eb
commit
454874a42d
3 changed files with 328 additions and 0 deletions
|
|
@ -234,6 +234,7 @@ mod jest {
|
||||||
mod react {
|
mod react {
|
||||||
pub mod button_has_type;
|
pub mod button_has_type;
|
||||||
pub mod checked_requires_onchange_or_readonly;
|
pub mod checked_requires_onchange_or_readonly;
|
||||||
|
pub mod iframe_missing_sandbox;
|
||||||
pub mod jsx_boolean_value;
|
pub mod jsx_boolean_value;
|
||||||
pub mod jsx_curly_brace_presence;
|
pub mod jsx_curly_brace_presence;
|
||||||
pub mod jsx_key;
|
pub mod jsx_key;
|
||||||
|
|
@ -771,6 +772,7 @@ oxc_macros::declare_all_lint_rules! {
|
||||||
promise::valid_params,
|
promise::valid_params,
|
||||||
react::button_has_type,
|
react::button_has_type,
|
||||||
react::checked_requires_onchange_or_readonly,
|
react::checked_requires_onchange_or_readonly,
|
||||||
|
react::iframe_missing_sandbox,
|
||||||
react::jsx_boolean_value,
|
react::jsx_boolean_value,
|
||||||
react::jsx_curly_brace_presence,
|
react::jsx_curly_brace_presence,
|
||||||
react::jsx_key,
|
react::jsx_key,
|
||||||
|
|
|
||||||
233
crates/oxc_linter/src/rules/react/iframe_missing_sandbox.rs
Normal file
233
crates/oxc_linter/src/rules/react/iframe_missing_sandbox.rs
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
use oxc_ast::ast::{
|
||||||
|
Argument, Expression, JSXAttributeItem, JSXAttributeValue, JSXElementName, ObjectProperty,
|
||||||
|
ObjectPropertyKind, StringLiteral,
|
||||||
|
};
|
||||||
|
use oxc_ast::AstKind;
|
||||||
|
use oxc_diagnostics::OxcDiagnostic;
|
||||||
|
use oxc_macros::declare_oxc_lint;
|
||||||
|
use oxc_span::Span;
|
||||||
|
use phf::{phf_set, Set};
|
||||||
|
|
||||||
|
use crate::utils::{get_prop_value, has_jsx_prop_ignore_case, is_create_element_call};
|
||||||
|
use crate::{context::LintContext, rule::Rule, AstNode};
|
||||||
|
|
||||||
|
fn missing_sandbox_prop(span: Span) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn("An iframe element is missing a sandbox attribute")
|
||||||
|
.with_help("Add a `sandbox` attribute to the `iframe` element.")
|
||||||
|
.with_label(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_sandbox_prop(span: Span, value: &str) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn(format!("An iframe element defines a sandbox attribute with invalid value: {value}"))
|
||||||
|
.with_help("Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.")
|
||||||
|
.with_label(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_sandbox_combination_prop(span: Span) -> OxcDiagnostic {
|
||||||
|
OxcDiagnostic::warn("An `iframe` element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid")
|
||||||
|
.with_help("Remove `allow-scripts` or `allow-same-origin`.")
|
||||||
|
.with_label(span)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALLOWED_VALUES: Set<&'static str> = phf_set! {
|
||||||
|
"",
|
||||||
|
"allow-downloads-without-user-activation",
|
||||||
|
"allow-downloads",
|
||||||
|
"allow-forms",
|
||||||
|
"allow-modals",
|
||||||
|
"allow-orientation-lock",
|
||||||
|
"allow-pointer-lock",
|
||||||
|
"allow-popups",
|
||||||
|
"allow-popups-to-escape-sandbox",
|
||||||
|
"allow-presentation",
|
||||||
|
"allow-same-origin",
|
||||||
|
"allow-scripts",
|
||||||
|
"allow-storage-access-by-user-activation",
|
||||||
|
"allow-top-navigation",
|
||||||
|
"allow-top-navigation-by-user-activation"
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct IframeMissingSandbox;
|
||||||
|
|
||||||
|
declare_oxc_lint!(
|
||||||
|
/// ### What it does
|
||||||
|
///
|
||||||
|
/// Enforce sandbox attribute on iframe elements
|
||||||
|
///
|
||||||
|
/// ### Why is this bad?
|
||||||
|
///
|
||||||
|
/// The sandbox attribute enables an extra set of restrictions for the content in the iframe. Using sandbox attribute is considered a good security practice.
|
||||||
|
/// To learn more about sandboxing, see [MDN's documentation on the `sandbox` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox).
|
||||||
|
|
||||||
|
///
|
||||||
|
/// This rule checks all React `<iframe>` elements and verifies that there is `sandbox` attribute and that it's value is valid. In addition to that it also reports cases where attribute contains `allow-scripts` and `allow-same-origin` at the same time as this combination allows the embedded document to remove the sandbox attribute and bypass the restrictions.
|
||||||
|
|
||||||
|
/// ### Examples
|
||||||
|
///
|
||||||
|
/// Examples of **incorrect** code for this rule:
|
||||||
|
/// ```jsx
|
||||||
|
/// <iframe/>;
|
||||||
|
/// <iframe sandbox="invalid-value" />;
|
||||||
|
/// <iframe sandbox="allow-same-origin allow-scripts"/>;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Examples of **correct** code for this rule:
|
||||||
|
/// ```jsx
|
||||||
|
/// <iframe sandbox="" />;
|
||||||
|
/// <iframe sandbox="allow-origin" />;
|
||||||
|
/// ```
|
||||||
|
IframeMissingSandbox,
|
||||||
|
correctness,
|
||||||
|
pending
|
||||||
|
);
|
||||||
|
|
||||||
|
impl Rule for IframeMissingSandbox {
|
||||||
|
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
|
||||||
|
match node.kind() {
|
||||||
|
AstKind::JSXOpeningElement(jsx_el) => {
|
||||||
|
let JSXElementName::Identifier(identifier) = &jsx_el.name else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if identifier.name != "iframe" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
has_jsx_prop_ignore_case(jsx_el, "sandbox").map_or_else(
|
||||||
|
|| {
|
||||||
|
ctx.diagnostic(missing_sandbox_prop(identifier.span));
|
||||||
|
},
|
||||||
|
|sandbox_prop| {
|
||||||
|
validate_sandbox_attribute(sandbox_prop, ctx);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AstKind::CallExpression(call_expr) => {
|
||||||
|
if is_create_element_call(call_expr) {
|
||||||
|
let Some(Argument::StringLiteral(str)) = call_expr.arguments.first() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if str.value != "iframe" {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Argument::ObjectExpression(obj_expr)) = call_expr.arguments.get(1) {
|
||||||
|
obj_expr
|
||||||
|
.properties
|
||||||
|
.iter()
|
||||||
|
.find_map(|prop| {
|
||||||
|
if let ObjectPropertyKind::ObjectProperty(prop) = prop {
|
||||||
|
if prop.key.is_specific_static_name("sandbox") {
|
||||||
|
return Some(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.map_or_else(
|
||||||
|
|| {
|
||||||
|
ctx.diagnostic(missing_sandbox_prop(obj_expr.span));
|
||||||
|
},
|
||||||
|
|sandbox_prop| {
|
||||||
|
validate_sandbox_property(sandbox_prop, ctx);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.diagnostic(missing_sandbox_prop(call_expr.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn validate_sandbox_value(literal: &StringLiteral, ctx: &LintContext) {
|
||||||
|
let attrs = literal.value.split(' ');
|
||||||
|
let mut has_allow_same_origin = false;
|
||||||
|
let mut has_allow_scripts = false;
|
||||||
|
for trimmed_atr in attrs.into_iter().map(str::trim) {
|
||||||
|
if !ALLOWED_VALUES.contains(trimmed_atr) {
|
||||||
|
ctx.diagnostic(invalid_sandbox_prop(literal.span, trimmed_atr));
|
||||||
|
}
|
||||||
|
if trimmed_atr == "allow-scripts" {
|
||||||
|
has_allow_scripts = true;
|
||||||
|
}
|
||||||
|
if trimmed_atr == "allow-same-origin" {
|
||||||
|
has_allow_same_origin = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if has_allow_scripts && has_allow_same_origin {
|
||||||
|
ctx.diagnostic(invalid_sandbox_combination_prop(literal.span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_sandbox_property(object_property: &ObjectProperty, ctx: &LintContext) {
|
||||||
|
if let Expression::StringLiteral(str) = object_property.value.without_parentheses() {
|
||||||
|
validate_sandbox_value(str, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn validate_sandbox_attribute(jsx_el: &JSXAttributeItem, ctx: &LintContext) {
|
||||||
|
if let Some(JSXAttributeValue::StringLiteral(str)) = get_prop_value(jsx_el) {
|
||||||
|
validate_sandbox_value(str, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
use crate::tester::Tester;
|
||||||
|
|
||||||
|
let pass = vec![
|
||||||
|
r#"<div sandbox="__unknown__" />;"#,
|
||||||
|
r#"<iframe sandbox="" />;"#,
|
||||||
|
r#"<iframe sandbox={""} />"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "" });"#,
|
||||||
|
r#"<iframe src="foo.htm" sandbox></iframe>"#,
|
||||||
|
r#"React.createElement("iframe", { src: "foo.htm", sandbox: true })"#,
|
||||||
|
r#"<iframe src="foo.htm" sandbox sandbox></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-forms"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-modals"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-orientation-lock"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-pointer-lock"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-popups"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-popups-to-escape-sandbox"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-presentation"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-same-origin"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-scripts"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-top-navigation"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-top-navigation-by-user-activation"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-forms allow-modals"></iframe>"#,
|
||||||
|
r#"<iframe sandbox="allow-popups allow-popups-to-escape-sandbox allow-pointer-lock allow-same-origin allow-top-navigation"></iframe>"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-forms" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-modals" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-orientation-lock" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-pointer-lock" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-popups" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-popups-to-escape-sandbox" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-presentation" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-same-origin" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-scripts" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-top-navigation" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-top-navigation-by-user-activation" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-forms allow-modals" })"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "allow-popups allow-popups-to-escape-sandbox allow-pointer-lock allow-same-origin allow-top-navigation" })"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
let fail = vec![
|
||||||
|
"<iframe></iframe>;",
|
||||||
|
"<iframe/>;",
|
||||||
|
r#"React.createElement("iframe");"#,
|
||||||
|
r#"React.createElement("iframe", {});"#,
|
||||||
|
r#"React.createElement("iframe", null);"#,
|
||||||
|
r#"<iframe sandbox="__unknown__"></iframe>"#,
|
||||||
|
r#"React.createElement("iframe", { sandbox: "__unknown__" })"#,
|
||||||
|
r#"<iframe sandbox="allow-popups __unknown__"/>"#,
|
||||||
|
r#"<iframe sandbox="__unknown__ allow-popups"/>"#,
|
||||||
|
r#"<iframe sandbox=" allow-forms __unknown__ allow-popups __unknown__ "/>"#,
|
||||||
|
r#"<iframe sandbox="allow-scripts allow-same-origin"></iframe>;"#,
|
||||||
|
r#"<iframe sandbox="allow-same-origin allow-scripts"/>;"#,
|
||||||
|
];
|
||||||
|
|
||||||
|
Tester::new(IframeMissingSandbox::NAME, pass, fail).test_and_snapshot();
|
||||||
|
}
|
||||||
93
crates/oxc_linter/src/snapshots/iframe_missing_sandbox.snap
Normal file
93
crates/oxc_linter/src/snapshots/iframe_missing_sandbox.snap
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
---
|
||||||
|
source: crates/oxc_linter/src/tester.rs
|
||||||
|
---
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element is missing a sandbox attribute
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:2]
|
||||||
|
1 │ <iframe></iframe>;
|
||||||
|
· ──────
|
||||||
|
╰────
|
||||||
|
help: Add a `sandbox` attribute to the `iframe` element.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element is missing a sandbox attribute
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:2]
|
||||||
|
1 │ <iframe/>;
|
||||||
|
· ──────
|
||||||
|
╰────
|
||||||
|
help: Add a `sandbox` attribute to the `iframe` element.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element is missing a sandbox attribute
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:1]
|
||||||
|
1 │ React.createElement("iframe");
|
||||||
|
· ─────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Add a `sandbox` attribute to the `iframe` element.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element is missing a sandbox attribute
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:31]
|
||||||
|
1 │ React.createElement("iframe", {});
|
||||||
|
· ──
|
||||||
|
╰────
|
||||||
|
help: Add a `sandbox` attribute to the `iframe` element.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element is missing a sandbox attribute
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:1]
|
||||||
|
1 │ React.createElement("iframe", null);
|
||||||
|
· ───────────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Add a `sandbox` attribute to the `iframe` element.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox="__unknown__"></iframe>
|
||||||
|
· ─────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:42]
|
||||||
|
1 │ React.createElement("iframe", { sandbox: "__unknown__" })
|
||||||
|
· ─────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox="allow-popups __unknown__"/>
|
||||||
|
· ──────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox="__unknown__ allow-popups"/>
|
||||||
|
· ──────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox=" allow-forms __unknown__ allow-popups __unknown__ "/>
|
||||||
|
· ─────────────────────────────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An iframe element defines a sandbox attribute with invalid value: __unknown__
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox=" allow-forms __unknown__ allow-popups __unknown__ "/>
|
||||||
|
· ─────────────────────────────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Check this link for the valid values of `sandbox` attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An `iframe` element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox="allow-scripts allow-same-origin"></iframe>;
|
||||||
|
· ─────────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Remove `allow-scripts` or `allow-same-origin`.
|
||||||
|
|
||||||
|
⚠ eslint-plugin-react(iframe-missing-sandbox): An `iframe` element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid
|
||||||
|
╭─[iframe_missing_sandbox.tsx:1:17]
|
||||||
|
1 │ <iframe sandbox="allow-same-origin allow-scripts"/>;
|
||||||
|
· ─────────────────────────────────
|
||||||
|
╰────
|
||||||
|
help: Remove `allow-scripts` or `allow-same-origin`.
|
||||||
Loading…
Reference in a new issue