diff --git a/apps/oxlint/src/command/lint.rs b/apps/oxlint/src/command/lint.rs index e444cb9f623e3..70dc0e76d17f0 100644 --- a/apps/oxlint/src/command/lint.rs +++ b/apps/oxlint/src/command/lint.rs @@ -283,10 +283,6 @@ pub struct EnablePlugins { /// Enable the node plugin and detect node usage problems #[bpaf(flag(OverrideToggle::Enable, OverrideToggle::NotSet), hide_usage)] pub node_plugin: OverrideToggle, - - /// Enable the security plugin and detect security problems - #[bpaf(flag(OverrideToggle::Enable, OverrideToggle::NotSet), hide_usage)] - pub security_plugin: OverrideToggle, } /// Enables or disables a boolean option, or leaves it unset. @@ -362,7 +358,6 @@ impl EnablePlugins { self.react_perf_plugin.inspect(|yes| plugins.set(LintPlugins::REACT_PERF, yes)); self.promise_plugin.inspect(|yes| plugins.set(LintPlugins::PROMISE, yes)); self.node_plugin.inspect(|yes| plugins.set(LintPlugins::NODE, yes)); - self.security_plugin.inspect(|yes| plugins.set(LintPlugins::SECURITY, yes)); // Without this, jest plugins adapted to vitest will not be enabled. if self.vitest_plugin.is_enabled() && self.jest_plugin.is_not_set() { diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index 7e6d7d09c0b6b..190d06fc96c6c 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -171,7 +171,7 @@ mod test { serde_json::from_str(r#"{ "plugins": ["typescript", "unicorn"] }"#).unwrap(); assert_eq!(config.plugins, LintPlugins::TYPESCRIPT.union(LintPlugins::UNICORN)); let config: Oxlintrc = - serde_json::from_str(r#"{ "plugins": ["typescript", "unicorn", "react", "oxc", "import", "jsdoc", "jest", "vitest", "jsx-a11y", "nextjs", "react-perf", "promise", "node", "security"] }"#).unwrap(); + serde_json::from_str(r#"{ "plugins": ["typescript", "unicorn", "react", "oxc", "import", "jsdoc", "jest", "vitest", "jsx-a11y", "nextjs", "react-perf", "promise", "node"] }"#).unwrap(); assert_eq!(config.plugins, LintPlugins::all()); let config: Oxlintrc = diff --git a/crates/oxc_linter/src/config/plugins.rs b/crates/oxc_linter/src/config/plugins.rs index 85cedb9e43306..794745411a584 100644 --- a/crates/oxc_linter/src/config/plugins.rs +++ b/crates/oxc_linter/src/config/plugins.rs @@ -38,8 +38,6 @@ bitflags! { const PROMISE = 1 << 11; /// `eslint-plugin-node` const NODE = 1 << 12; - /// Custom security rules made by the Oxc team - const SECURITY = 1 << 13; } } impl Default for LintPlugins { @@ -65,7 +63,6 @@ impl From for LintPlugins { plugins.set(LintPlugins::REACT_PERF, options.react_perf); plugins.set(LintPlugins::PROMISE, options.promise); plugins.set(LintPlugins::NODE, options.node); - plugins.set(LintPlugins::SECURITY, options.security); plugins } } @@ -115,7 +112,6 @@ impl From<&str> for LintPlugins { "react-perf" | "react_perf" => LintPlugins::REACT_PERF, "promise" => LintPlugins::PROMISE, "node" => LintPlugins::NODE, - "security" | "oxc-security" => LintPlugins::SECURITY, // "eslint" is not really a plugin, so it's 'empty'. This has the added benefit of // making it the default value. _ => LintPlugins::empty(), @@ -139,7 +135,6 @@ impl From for &'static str { LintPlugins::REACT_PERF => "react-perf", LintPlugins::PROMISE => "promise", LintPlugins::NODE => "node", - LintPlugins::SECURITY => "security", _ => "", } } @@ -245,7 +240,6 @@ pub struct LintPluginOptions { pub react_perf: bool, pub promise: bool, pub node: bool, - pub security: bool, } impl Default for LintPluginOptions { @@ -264,7 +258,6 @@ impl Default for LintPluginOptions { react_perf: false, promise: false, node: false, - security: false, } } } @@ -287,7 +280,6 @@ impl LintPluginOptions { react_perf: false, promise: false, node: false, - security: false, } } @@ -308,7 +300,6 @@ impl LintPluginOptions { react_perf: true, promise: true, node: true, - security: true, } } } @@ -332,7 +323,6 @@ impl> FromIterator<(S, bool)> for LintPluginOptions { LintPlugins::REACT_PERF => options.react_perf = enabled, LintPlugins::PROMISE => options.promise = enabled, LintPlugins::NODE => options.node = enabled, - LintPlugins::SECURITY => options.security = enabled, _ => {} // ignored } } @@ -365,7 +355,6 @@ mod test { && self.react_perf == other.react_perf && self.promise == other.promise && self.node == other.node - && self.security == other.security } } @@ -405,7 +394,6 @@ mod test { react_perf: false, promise: false, node: false, - security: false, }; assert_eq!(plugins, expected); } diff --git a/crates/oxc_linter/src/context/mod.rs b/crates/oxc_linter/src/context/mod.rs index ea63e0c87daf7..2a891195a41bf 100644 --- a/crates/oxc_linter/src/context/mod.rs +++ b/crates/oxc_linter/src/context/mod.rs @@ -350,5 +350,4 @@ const PLUGIN_PREFIXES: phf::Map<&'static str, &'static str> = phf::phf_map! { "unicorn" => "eslint-plugin-unicorn", "vitest" => "eslint-plugin-vitest", "node" => "eslint-plugin-node", - "security" => "oxc-security", }; diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 11cdfc70de79a..8a1c0686db7b7 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -443,10 +443,6 @@ mod oxc { pub mod uninvoked_array_callback; } -mod security { - pub mod api_keys; -} - mod nextjs { pub mod google_font_display; pub mod google_font_preconnect; @@ -850,7 +846,6 @@ oxc_macros::declare_all_lint_rules! { react_perf::jsx_no_new_array_as_prop, react_perf::jsx_no_new_function_as_prop, react_perf::jsx_no_new_object_as_prop, - security::api_keys, typescript::adjacent_overload_signatures, typescript::array_type, typescript::ban_ts_comment, diff --git a/crates/oxc_linter/src/rules/security/api_keys/entropy.rs b/crates/oxc_linter/src/rules/security/api_keys/entropy.rs deleted file mode 100644 index edb91afdf0041..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/entropy.rs +++ /dev/null @@ -1,65 +0,0 @@ -/// Calculates the Shannon entropy of a byte string. -/// -/// Implementation borrowed from [Rosetta Code](https://rosettacode.org/wiki/Entropy#Rust). -/// -/// see: [Entropy (Wikipedial)](https://en.wikipedia.org/wiki/Entropy_(information_theory)) -#[allow(clippy::cast_precision_loss)] -pub(crate) fn entropy>(string: S) -> f32 { - let mut histogram = [0u32; 256]; - let bytes = string.as_ref(); - // we don't care if this is truncated - let len = bytes.len() as f32; - - for &b in bytes { - histogram[b as usize] += 1; - } - - histogram - .iter() - .copied() - .filter(|&h| h != 0) - .map(|h| h as f32 / len) // we don't care if this is truncated - .map(|ratio| -ratio * ratio.log2()) - .sum() -} - -pub(crate) trait Entropy { - /// Calculates the Shannon entropy of a byte string. - /// - /// Implementation borrowed from [Rosetta Code](https://rosettacode.org/wiki/Entropy#Rust). - /// - /// see: [Entropy (Wikipedial)](https://en.wikipedia.org/wiki/Entropy_(information_theory)) - fn entropy(&self) -> f32; -} - -impl Entropy for S -where - S: AsRef<[u8]>, -{ - fn entropy(&self) -> f32 { - entropy(self) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_entropy() { - let test_cases = vec![ - ("hello world", "hello world".entropy()), - ("hello world", b"hello world".entropy()), - ("hello world", String::from("hello world").entropy()), - ("hello world", 2.845_351_2), - ]; - - for (input, expected) in test_cases { - let actual = entropy(input); - assert!( - (actual - expected).abs() < f32::EPSILON, - "expected entropy({input}) to be {expected}, got {actual}" - ); - } - } -} diff --git a/crates/oxc_linter/src/rules/security/api_keys/mod.rs b/crates/oxc_linter/src/rules/security/api_keys/mod.rs deleted file mode 100644 index 936e5e1392e00..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/mod.rs +++ /dev/null @@ -1,232 +0,0 @@ -mod entropy; -#[allow(unused_imports, unused_variables)] -mod secret; -mod secrets; - -use std::{num::NonZeroU32, ops::Deref}; - -use regex::Regex; -use serde::Deserialize; -use serde_json::Value; - -use oxc_ast::AstKind; -use oxc_diagnostics::OxcDiagnostic; -use oxc_macros::declare_oxc_lint; -use oxc_span::{CompactStr, GetSpan}; - -use crate::{context::LintContext, rule::Rule, AstNode}; -use entropy::Entropy; -use secret::{ - Secret, SecretScanner, SecretScannerMeta, SecretViolation, DEFAULT_MIN_ENTROPY, DEFAULT_MIN_LEN, -}; -use secrets::{CustomSecret, SecretsEnum, ALL_RULES}; - -fn api_keys(violation: &SecretViolation) -> OxcDiagnostic { - OxcDiagnostic::warn(violation.message().to_owned()) - .with_error_code_num(format!("api-keys/{}", violation.rule_name())) - .with_label(violation.span()) - .with_help( - "Use a secrets manager to store your API keys securely, then read them at runtime.", - ) -} - -declare_oxc_lint!( - /// ### What it does - /// - /// Disallows hard-coded API keys and other credentials. - /// - /// ### Why is this bad? - /// - /// Hard-coding API keys and committing them to source control is a serious - /// security risk. - /// - /// 1. If your code is leaked, attackers can use your API keys to access your - /// services and data. - /// 2. Accidental bundling of API keys can lead them to be exposed publicly - /// in your website, compriming your services. - /// 3. Any developer or contractor you hire will have access to your - /// services, even after they lose access to your codebase. - /// 4. Even after being deleted, they will be visible in your git repo's - /// commit history. - /// 5. Key rotation requires a code change and redeployment, and can - /// therefore not be handled by security teams or by automated systems. - /// 6. Many, many more reasons. - /// - /// ```ts - /// const API_KEY = 'abcdef123456'; - /// const data = await fetch('/api/some/endpoint', { - /// headers: { - /// 'Authorization': `Bearer ${API_KEY}`, - /// } - /// }); - /// ``` - /// - /// ### What To Do Instead - /// - /// :::warning - /// The Oxc team are not security experts. We do not endorse any particular - /// key management service or strategy. Do your research and choose the best - /// solution/architecture for your use case. - /// ::: - /// - /// One possible alternative is to store secrets in a secure secrets manager - /// (such as [AWS - /// KMS](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/kms/), - /// [HashiCorp Vault](https://github.com/nodevault/node-vault/tree/v0.10.2), - /// [Pangea](https://pangea.cloud/docs/sdk/js/vault#retrieve), etc.) and - /// request them when your application starts (e.g. a Docker container, an - /// EC2). - /// - /// ### Examples - /// - /// Examples of **incorrect** code for this rule: - /// ```js - /// const AWS_ACCESS_KEY_ID = 'AKIA1234X678C123B567'; - /// const OPENAI_API_KEY = 'sk_test_1234567890'; - /// ``` - /// - /// Examples of **correct** code for this rule: - /// - /// ```js - /// const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; - /// const OPENAI_API_KEY = await getSecret('open-ai-api-key'); - /// ``` - ApiKeys, - correctness -); - -#[derive(Debug, Default, Clone)] -pub struct ApiKeys(Box); - -#[derive(Debug, Clone)] -pub struct ApiKeysInner { - /// Minimum length over all enabled secret rules. - /// This is a performance optimization to avoid checking each rule for every string. - min_len: NonZeroU32, - /// Minimum entropy over all enabled secret rules. - /// This is a performance optimization to avoid checking each rule for every string. - min_entropy: f32, - /// Credentials the user wants to check for. - rules: Vec, -} - -#[derive(Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiKeysConfig { - #[serde(default)] - custom_patterns: Vec, -} - -#[derive(Debug, Deserialize)] -struct CustomPattern { - // required fields - #[serde(rename = "ruleName")] - rule_name: CompactStr, - pattern: String, - - // optional fields - #[serde(default)] - message: Option, - #[serde(default)] - entropy: Option, - #[serde(default, rename = "minLength")] - min_len: Option, - #[serde(default, rename = "maxLength")] - max_len: Option, -} - -impl Default for ApiKeysInner { - fn default() -> Self { - Self::new(ALL_RULES.clone()) - } -} - -impl ApiKeysInner { - // TODO: allow configuring what rules are enabled/disabled - // TODO: allow custom patterns - pub fn new(rules: Vec) -> Self { - let min_len = rules.iter().map(secrets::SecretsEnum::min_len).min().unwrap(); - // can't use min() b/c f32 is not Ord - let min_entropy = rules.iter().map(secrets::SecretsEnum::min_entropy).fold(0.0, f32::min); - - Self { min_len, min_entropy, rules } - } -} - -impl Deref for ApiKeys { - type Target = ApiKeysInner; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ApiKeysInner {} - -impl Rule for ApiKeys { - fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { - let string: &'a str = match node.kind() { - AstKind::StringLiteral(string) => string.value.as_str(), - AstKind::TemplateLiteral(string) => { - let Some(string) = string.quasi() else { - return; - }; - string.as_str() - } - _ => return, - }; - - // skip strings that are below the length/entropy threshold of _all_ rules. Perf - // optimization, avoid O(n) len/entropy checks (for n rules) - if string.len() < self.min_len.get() as usize { - return; - } - let candidate = Secret::new(string, node.span(), None); - if candidate.entropy() < self.min_entropy { - return; - } - - for rule in &self.rules { - // order here is important: they're in order of cheapest to most expensive - if candidate.len() < rule.min_len().get() as usize - || candidate.entropy() < rule.min_entropy() - || rule.max_len().is_some_and(|max_len| candidate.len() > max_len.get() as usize) - || !rule.detect(&candidate) - { - continue; - } - - // This clone allocs no memory and so is relatively cheap. rustc should optimize it - // away anyways. - let mut violation = SecretViolation::new(candidate.clone(), rule); - if rule.verify(&mut violation) { - ctx.diagnostic(api_keys(&violation)); - return; - } - } - } - - fn from_configuration(value: Value) -> Self { - let Some(obj) = value.get(0) else { - return Self::default(); - }; - let config = serde_json::from_value::(obj.clone()) - .expect("Invalid configuration for 'oxc-security/api-keys'"); - - // TODO: Check if this is worth optimizing, then do so if needed. - let mut rules = ALL_RULES.clone(); - rules.extend(config.custom_patterns.into_iter().map(|pattern| { - let regex = Regex::new(&pattern.pattern) - .expect("Invalid custom API key regex in 'oxc-security/api-keys'"); - SecretsEnum::Custom(CustomSecret { - rule_name: pattern.rule_name, - message: pattern.message.unwrap_or("Detected a hard-coded secret.".into()), - entropy: pattern.entropy.unwrap_or(DEFAULT_MIN_ENTROPY), - min_len: pattern.min_len.unwrap_or(DEFAULT_MIN_LEN), - max_len: pattern.max_len, - pattern: regex, - }) - })); - - Self(Box::new(ApiKeysInner::new(rules))) - } -} diff --git a/crates/oxc_linter/src/rules/security/api_keys/secret.rs b/crates/oxc_linter/src/rules/security/api_keys/secret.rs deleted file mode 100644 index d2953c1260c59..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/secret.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::{borrow::Cow, num::NonZeroU32, ops::Deref}; - -use oxc_span::{Atom, GetSpan, Span}; - -use super::{Entropy, SecretsEnum}; - -/// A credential discovered in source code. -/// -/// Could be an API key, an auth token, or any other sensitive information. -#[allow(clippy::struct_field_names)] -#[derive(Debug, Clone)] -pub struct Secret<'a> { - secret: &'a str, - /// Secret span - span: Span, - /// TODO: find and pass identifiers once we have rules that need it - #[allow(dead_code)] - identifier: Option>, - entropy: f32, -} - -/// A secret that was positively identified by a secret rule. -/// -/// This gets used to construct the final diagnostic message. -#[derive(Debug, Clone)] -pub struct SecretViolation<'a> { - // NOTE: Rules get a &mut reference to a SecretViolation to verify the - // violation. It is important that the underlying secret is not modified. - secret: Secret<'a>, - rule_name: Cow<'a, str>, // really should be &'static - message: Cow<'a, str>, // really should be &'static -} - -// SAFETY: 8 is a valid value for NonZeroU32 -pub(super) const DEFAULT_MIN_LEN: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(8) }; -pub(super) const DEFAULT_MIN_ENTROPY: f32 = 0.5; - -/// Metadata trait separated out of [`SecretScanner`]. The easiest way to implement this is with -/// the [`oxc_macros::declare_oxc_secret!`] macro. -pub trait SecretScannerMeta { - /// Human-readable unique identifier describing what service this rule finds api keys for. - /// Must be kebab-case. - fn rule_name(&self) -> Cow<'static, str>; - - fn message(&self) -> Cow<'static, str>; - - /// Min str length a key candidate must have to be considered a violation. Must be >= 1. - #[inline] - fn min_len(&self) -> NonZeroU32 { - DEFAULT_MIN_LEN - } - - /// Secret candidates above this length will not be considered. - /// - /// By default, no maximum length is enforced. - #[inline] - fn max_len(&self) -> Option { - None - } - - /// Min entropy a key must have to be considered a violation. Must be >= 0. - /// - /// Defaults to 0.5 - #[inline] - fn min_entropy(&self) -> f32 { - DEFAULT_MIN_ENTROPY - } -} - -/// Detects hard-coded API keys and other credentials of a single kind or for a single SaaS -/// service. -pub trait SecretScanner: SecretScannerMeta { - /// Returns `true` if `candidate` is a leaked credential. - fn detect(&self, candidate: &Secret<'_>) -> bool; - - /// `verify` lets secret rules modify diagnostic messages and/or perform additional - /// verification checks on secrets before they are reported. You may mutate state such as the - /// diagnostic message, but you _must not_ modify the secret itself as it is shared between - /// all rules. - /// - /// Returns `true` to report the violation, or `false` to ignore it. - #[inline] - fn verify(&self, violation: &mut SecretViolation<'_>) -> bool { - true - } -} - -impl<'a> Secret<'a> { - pub fn new(secret: &'a str, span: Span, identifier: Option>) -> Self { - let entropy = secret.entropy(); - Self { secret, span, identifier, entropy } - } -} -impl Deref for Secret<'_> { - type Target = str; - - #[inline] - fn deref(&self) -> &Self::Target { - self.secret - } -} - -impl Entropy for Secret<'_> { - #[inline] - fn entropy(&self) -> f32 { - self.entropy - } -} - -impl GetSpan for Secret<'_> { - #[inline] - fn span(&self) -> Span { - self.span - } -} - -impl<'a> SecretViolation<'a> { - pub fn new(secret: Secret<'a>, rule: &SecretsEnum) -> Self { - Self { secret, rule_name: rule.rule_name(), message: rule.message() } - } - - pub fn message(&self) -> &str { - &self.message - } - - pub fn set_message>>(&mut self, message: S) { - self.message = message.into(); - } - - pub fn rule_name(&self) -> &str { - &self.rule_name - } -} - -impl GetSpan for SecretViolation<'_> { - #[inline] - fn span(&self) -> Span { - self.secret.span() - } -} -impl Deref for SecretViolation<'_> { - type Target = str; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.secret - } -} diff --git a/crates/oxc_linter/src/rules/security/api_keys/secrets.rs b/crates/oxc_linter/src/rules/security/api_keys/secrets.rs deleted file mode 100644 index 4b9551851334d..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/secrets.rs +++ /dev/null @@ -1,71 +0,0 @@ -mod aws_access_token; -mod custom; - -use std::{borrow::Cow, num::NonZeroU32}; - -use super::{Secret, SecretScanner, SecretScannerMeta, SecretViolation}; - -pub use custom::CustomSecret; - -#[derive(Debug, Clone)] -pub enum SecretsEnum { - AwsAccessKeyId(aws_access_token::AwsAccessToken), - Custom(custom::CustomSecret), -} - -impl SecretsEnum { - pub fn rule_name(&self) -> Cow<'static, str> { - match self { - Self::AwsAccessKeyId(rule) => rule.rule_name(), - Self::Custom(rule) => rule.rule_name(), - } - } - - pub fn message(&self) -> Cow<'static, str> { - match self { - Self::AwsAccessKeyId(rule) => rule.message(), - Self::Custom(rule) => rule.message(), - } - } - - pub fn min_len(&self) -> NonZeroU32 { - match self { - Self::AwsAccessKeyId(rule) => rule.min_len(), - Self::Custom(rule) => rule.min_len(), - } - } - - pub fn max_len(&self) -> Option { - match self { - Self::AwsAccessKeyId(rule) => rule.max_len(), - Self::Custom(rule) => rule.max_len(), - } - } - - pub fn min_entropy(&self) -> f32 { - match self { - Self::AwsAccessKeyId(rule) => rule.min_entropy(), - Self::Custom(rule) => rule.min_entropy(), - } - } - - pub fn verify(&self, violation: &mut SecretViolation<'_>) -> bool { - match self { - Self::AwsAccessKeyId(rule) => rule.verify(violation), - Self::Custom(rule) => rule.verify(violation), - } - } - - pub fn detect(&self, candidate: &Secret<'_>) -> bool { - match self { - Self::AwsAccessKeyId(rule) => rule.detect(candidate), - Self::Custom(rule) => rule.detect(candidate), - } - } -} - -lazy_static::lazy_static! { - pub static ref ALL_RULES: Vec = vec![ - SecretsEnum::AwsAccessKeyId(aws_access_token::AwsAccessToken), - ]; -} diff --git a/crates/oxc_linter/src/rules/security/api_keys/secrets/aws_access_token.rs b/crates/oxc_linter/src/rules/security/api_keys/secrets/aws_access_token.rs deleted file mode 100644 index 9e2466ce62929..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/secrets/aws_access_token.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::num::NonZeroU32; - -use oxc_macros::declare_oxc_secret; -use phf::{map::Map, phf_map}; - -use super::{Secret, SecretScanner, SecretViolation}; - -/// See: -#[derive(Debug, Default, Clone)] -pub struct AwsAccessToken; - -declare_oxc_secret! { - AwsAccessToken, - "Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources.", - entropy = 2.0, - min_len = 20, - max_len = 20, -} - -impl SecretScanner for AwsAccessToken { - // '''(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA|)[A-Z0-9]{16}''' - fn detect(&self, candidate: &Secret<'_>) -> bool { - if !candidate.chars().all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()) { - return false; - } - - let prefix = &candidate[..4]; - AWS_TOKEN_PREFIXES.contains_key(prefix) || &prefix[0..3] == "A3T" - } - - fn verify(&self, violation: &mut SecretViolation<'_>) -> bool { - let prefix = &violation[..4]; - // Detect false positives on DNA sequences - if prefix == "ACCA" && violation.chars().all(|c| c.is_ascii_uppercase()) { - return false; - } - - let name = AWS_TOKEN_PREFIXES.get(prefix).copied().unwrap_or("AWS access token"); - let a_or_an = match name.chars().next().unwrap() { - 'A' | 'E' | 'I' | 'O' | 'U' => "an", - _ => "a", - }; - - violation.set_message(format!( - "Detected {a_or_an} {name}, which may lead to unauthorized access to AWS resources." - )); - - true - } -} - -/// List taken from: -/// -static AWS_TOKEN_PREFIXES: Map<&'static str, &'static str> = phf_map! { - "ABIA" => "AWS STS service bearer token", - "ACCA" => "AWS Context-specific credential", - "AGPA" => "AWS User Group ID", - "AIDA" => "AWS IAM User ID", - "AIPA" => "Amazon EC2 instance profile", - "AKIA" => "AWS Access Key ID", - "ANPA" => "managed AWS Policy ID", - "ANVA" => "managed AWS Policy Version ID", - "APKA" => "AWS Public key", - "AROA" => "AWS Role", - "ASCA" => "AWS Certificate", - "ASIA" => "temporary (AWS STS) Access Key", -}; - -#[test] -fn test() { - use crate::{rules::SecurityApiKeys, tester::Tester, RuleMeta}; - - let pass = vec![ - "let x = ''", - "let x = 'AKIA'", - "let not_a_key = 'abcdabcdabcdabcdabcd' ", // no prefix, has lowercase - "let not_a_key = 'AKIA ABCD1099FAM9KEY' ", // whitespace - "let not_a_key = 'AKIA-ABCD1099FAM9KEY' ", // special characters - "let not_a_key = 'AKIA_ABCD1099FAM9KEY' ", // special characters - "let not_a_key = 'AKIA%ABCD1099FAM9KEY' ", // special characters - "let not_a_key = 'AKIA$ABCD1099FAM9KEY' ", // special characters - "let not_a_key = 'AKIAAABcD1099FAM9KEY' ", // has lowercase - "let not_a_key = 'AKIAAABCD1099FAM9KEY9'", // too long - "let dna = 'ACCATGGCTACCGCTGTGCT' ", // DNA sequence - ]; - - let fail = vec![ - r#"let key = "AKIAAABCD1099FAM9KEY""#, - "let key = `AKIAAABCD1099FAM9KEY`", // no-expression template literal - "let key = 'ABIAAABCD1099FAM9KEY'", - "let key = 'ACCAAABCD1099FAM9KEY'", - "let key = 'AKIAAABCD1099FAM9KEY'", - "let key = 'AKIAAABCD1099FAM9KEY'", - "let key = 'AKIAAABCD1099FAM9KEY'", - ]; - - Tester::new(SecurityApiKeys::NAME, SecurityApiKeys::CATEGORY, pass, fail) - .with_snapshot_suffix("aws_access_token") - .test_and_snapshot(); -} diff --git a/crates/oxc_linter/src/rules/security/api_keys/secrets/custom.rs b/crates/oxc_linter/src/rules/security/api_keys/secrets/custom.rs deleted file mode 100644 index 939b571ecd953..0000000000000 --- a/crates/oxc_linter/src/rules/security/api_keys/secrets/custom.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{borrow::Cow, num::NonZeroU32}; - -use regex::Regex; - -use oxc_span::CompactStr; - -use super::{Secret, SecretScanner, SecretScannerMeta}; - -#[derive(Debug, Clone)] -pub struct CustomSecret { - pub(crate) rule_name: CompactStr, - pub(crate) message: CompactStr, - pub(crate) entropy: f32, - pub(crate) min_len: NonZeroU32, - pub(crate) max_len: Option, - pub(crate) pattern: Regex, -} - -impl SecretScannerMeta for CustomSecret { - fn rule_name(&self) -> Cow<'static, str> { - self.rule_name.clone().into() - } - fn message(&self) -> Cow<'static, str> { - self.message.clone().into() - } - fn min_len(&self) -> NonZeroU32 { - self.min_len - } - fn max_len(&self) -> Option { - self.max_len - } - fn min_entropy(&self) -> f32 { - self.entropy - } -} - -impl SecretScanner for CustomSecret { - fn detect(&self, candidate: &Secret<'_>) -> bool { - self.pattern.is_match(candidate) - } -} diff --git a/crates/oxc_linter/src/snapshots/eslint_no_object_constructor.snap b/crates/oxc_linter/src/snapshots/eslint_no_object_constructor.snap index f6d313176e5ca..2c017db55feed 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_object_constructor.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_object_constructor.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint(no-object-constructor): Disallow calls to the `Object` constructor without an argument ╭─[no_object_constructor.tsx:1:1] diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_expressions.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_expressions.snap index 71af5bbb6c115..f8be5f3347f51 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_expressions.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_expressions.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint(no-unused-expressions): Disallow unused expressions ╭─[no_unused_expressions.tsx:1:1] diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap index fde489e394336..8276bdb3ccb80 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@eslint.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint(no-unused-vars): Function 'foox' is declared but never used. ╭─[no_unused_vars.tsx:1:10] diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-used-declarations.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-used-declarations.snap index 2adf1447d2f84..03f218bf6f1cb 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-used-declarations.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-used-declarations.snap @@ -1,4 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- diff --git a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-reassignment.snap b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-reassignment.snap index e7dc351d62ecb..4699947028248 100644 --- a/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-reassignment.snap +++ b/crates/oxc_linter/src/snapshots/eslint_no_unused_vars@oxc-vars-reassignment.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint(no-unused-vars): Variable 'a' is assigned a value but never used. Unused variables should start with a '_'. ╭─[no_unused_vars.tsx:1:5] diff --git a/crates/oxc_linter/src/snapshots/eslint_prefer_spread.snap b/crates/oxc_linter/src/snapshots/eslint_prefer_spread.snap index 62d967e7ac85b..dc2fa8ed0aa9c 100644 --- a/crates/oxc_linter/src/snapshots/eslint_prefer_spread.snap +++ b/crates/oxc_linter/src/snapshots/eslint_prefer_spread.snap @@ -1,6 +1,5 @@ --- source: crates/oxc_linter/src/tester.rs -assertion_line: 353 snapshot_kind: text --- ⚠ eslint(prefer-spread): Require spread operators instead of .apply() diff --git a/crates/oxc_linter/src/snapshots/import_no_namespace.snap b/crates/oxc_linter/src/snapshots/import_no_namespace.snap index 03d166ff05263..6100041c64543 100644 --- a/crates/oxc_linter/src/snapshots/import_no_namespace.snap +++ b/crates/oxc_linter/src/snapshots/import_no_namespace.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-import(no-namespace): Usage of namespaced aka wildcard "*" imports prohibited ╭─[index.js:1:13] diff --git a/crates/oxc_linter/src/snapshots/jsx_a11y_anchor_ambiguous_text.snap b/crates/oxc_linter/src/snapshots/jsx_a11y_anchor_ambiguous_text.snap index 91bd6b1b715f4..499fb0e34a575 100644 --- a/crates/oxc_linter/src/snapshots/jsx_a11y_anchor_ambiguous_text.snap +++ b/crates/oxc_linter/src/snapshots/jsx_a11y_anchor_ambiguous_text.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-jsx-a11y(anchor-ambiguous-text): Unexpected ambagious anchor link text. ╭─[anchor_ambiguous_text.tsx:1:1] diff --git a/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap b/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap index ff1b076dbed44..f6cc782939f09 100644 --- a/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap +++ b/crates/oxc_linter/src/snapshots/oxc_const_comparisons.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ oxc(const-comparisons): Unexpected constant comparison ╭─[const_comparisons.tsx:1:1] diff --git a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap index 45552be6d85df..1aae849e806b5 100644 --- a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap +++ b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useCallback has a missing dependency: 'props.foo' ╭─[exhaustive_deps.tsx:4:14] diff --git a/crates/oxc_linter/src/snapshots/react_jsx_no_script_url.snap b/crates/oxc_linter/src/snapshots/react_jsx_no_script_url.snap index 68dc58dbf73e8..fe009c417aa4a 100644 --- a/crates/oxc_linter/src/snapshots/react_jsx_no_script_url.snap +++ b/crates/oxc_linter/src/snapshots/react_jsx_no_script_url.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-react(jsx-no-script-url): A future version of React will block javascript: URLs as a security precaution. ╭─[jsx_no_script_url.tsx:1:4] diff --git a/crates/oxc_linter/src/snapshots/react_no_array_index_key.snap b/crates/oxc_linter/src/snapshots/react_no_array_index_key.snap index a9cc273876cc7..93cabb8242984 100644 --- a/crates/oxc_linter/src/snapshots/react_no_array_index_key.snap +++ b/crates/oxc_linter/src/snapshots/react_no_array_index_key.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-react(no-array-index-key): Usage of Array index in keys is not allowed ╭─[no_array_index_key.tsx:2:20] diff --git a/crates/oxc_linter/src/snapshots/react_rules_of_hooks.snap b/crates/oxc_linter/src/snapshots/react_rules_of_hooks.snap index de971c9841845..be7e8c86c1ac1 100644 --- a/crates/oxc_linter/src/snapshots/react_rules_of_hooks.snap +++ b/crates/oxc_linter/src/snapshots/react_rules_of_hooks.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-react-hooks(rules-of-hooks): React Hook "useConditionalHook" is called conditionally. React Hooks must be called in the exact same order in every component render. ╭─[rules_of_hooks.tsx:4:18] diff --git a/crates/oxc_linter/src/snapshots/security_api_keys@aws_access_token.snap b/crates/oxc_linter/src/snapshots/security_api_keys@aws_access_token.snap deleted file mode 100644 index 65c9da5091aa7..0000000000000 --- a/crates/oxc_linter/src/snapshots/security_api_keys@aws_access_token.snap +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: crates/oxc_linter/src/tester.rs -snapshot_kind: text ---- - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = "AKIAAABCD1099FAM9KEY" - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = `AKIAAABCD1099FAM9KEY` - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS STS service bearer token, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = 'ABIAAABCD1099FAM9KEY' - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Context-specific credential, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = 'ACCAAABCD1099FAM9KEY' - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = 'AKIAAABCD1099FAM9KEY' - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = 'AKIAAABCD1099FAM9KEY' - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. - - ⚠ oxc-security(api-keys/aws-access-token): Detected an AWS Access Key ID, which may lead to unauthorized access to AWS resources. - ╭─[api_keys.tsx:1:11] - 1 │ let key = 'AKIAAABCD1099FAM9KEY' - · ────────────────────── - ╰──── - help: Use a secrets manager to store your API keys securely, then read them at runtime. diff --git a/crates/oxc_linter/src/snapshots/typescript_no_inferrable_types.snap b/crates/oxc_linter/src/snapshots/typescript_no_inferrable_types.snap index 877675037a1be..6b3b0bfa93178 100644 --- a/crates/oxc_linter/src/snapshots/typescript_no_inferrable_types.snap +++ b/crates/oxc_linter/src/snapshots/typescript_no_inferrable_types.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ typescript-eslint(no-inferrable-types): Type can be trivially inferred from the initializer ╭─[no_inferrable_types.tsx:1:8] diff --git a/crates/oxc_linter/src/snapshots/typescript_no_require_imports.snap b/crates/oxc_linter/src/snapshots/typescript_no_require_imports.snap index 4a808bed3583d..8f192be6b1040 100644 --- a/crates/oxc_linter/src/snapshots/typescript_no_require_imports.snap +++ b/crates/oxc_linter/src/snapshots/typescript_no_require_imports.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ typescript-eslint(no-require-imports): Expected "import" statement instead of "require" call ╭─[no_require_imports.ts:1:11] diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_array_some.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_array_some.snap index c019faaff61d5..8d0be0bb13373 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_prefer_array_some.snap +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_array_some.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-unicorn(prefer-array-some): Prefer `.some(…)` over `.find(…)` or `.findLast(…)`. ╭─[prefer_array_some.tsx:1:9] diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_negative_index.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_negative_index.snap index fe9f106499b78..84ad8356a310d 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_prefer_negative_index.snap +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_negative_index.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-unicorn(prefer-negative-index): Prefer negative index over .length - index when possible ╭─[prefer_negative_index.tsx:1:1] diff --git a/crates/oxc_linter/src/snapshots/unicorn_prefer_set_has.snap b/crates/oxc_linter/src/snapshots/unicorn_prefer_set_has.snap index 9759ca1215ebb..40c924b4cb84b 100644 --- a/crates/oxc_linter/src/snapshots/unicorn_prefer_set_has.snap +++ b/crates/oxc_linter/src/snapshots/unicorn_prefer_set_has.snap @@ -1,5 +1,6 @@ --- source: crates/oxc_linter/src/tester.rs +snapshot_kind: text --- ⚠ eslint-plugin-unicorn(prefer-set-has): should be a `Set`, and use `.has()` to check existence or non-existence. ╭─[prefer_set_has.tsx:2:10] diff --git a/crates/oxc_macros/src/declare_oxc_secret.rs b/crates/oxc_macros/src/declare_oxc_secret.rs deleted file mode 100644 index 7b911a24512f3..0000000000000 --- a/crates/oxc_macros/src/declare_oxc_secret.rs +++ /dev/null @@ -1,155 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse::Parse, Ident, LitFloat, LitInt, LitStr, Token}; - -use super::{ - declare_oxc_lint::rule_name_converter, - util::{eat_comma, parse_assert}, -}; - -pub struct SecretRuleMeta { - struct_name: Ident, - message: LitStr, - entropy: Option, - min_len: Option, - max_len: Option, -} - -impl Parse for SecretRuleMeta { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let struct_name = input.parse()?; - input.parse::()?; - let description = input.parse()?; - - eat_comma(&input)?; - - let mut rule = SecretRuleMeta { - struct_name, - message: description, - entropy: None, - min_len: None, - max_len: None, - }; - - while input.peek(Ident) { - let ident = input.parse::()?; - #[allow(clippy::neg_cmp_op_on_partial_ord)] - match ident.to_string().as_str() { - "entropy" => { - input.parse::()?; - let entropy = input.parse::()?; - parse_assert!( - entropy.base10_parse::()? >= 0.0, - entropy, - "Entropy must be greater than or equal to 0." - ); - rule.entropy = Some(entropy); - } - "min_len" => { - input.parse::()?; - let min_len = input.parse::()?; - parse_assert!( - min_len.base10_parse::()? > 0, - min_len, - "Minimum length must be greater than or equal to 1." - ); - rule.min_len = Some(min_len); - } - "max_len" => { - input.parse::()?; - let max_len = input.parse::()?; - parse_assert!( - max_len.base10_parse::()? > 0, - max_len, - "Maximum length cannot be zero." - ); - rule.max_len = Some(max_len); - } - _ => parse_assert!( - false, - ident, - "Unexpected attribute. Only `entropy`, `min_len`, and `max_len` are allowed." - ), - } - eat_comma(&input)?; - } - - // Ignore the rest - input.parse::()?; - - if let (Some(min), Some(max)) = (rule.min_len.as_ref(), &rule.max_len.as_ref()) { - let min = min.base10_parse::()?; - let max = max.base10_parse::()?; - parse_assert!( - min <= max, - max, - "Maximum length must be greater than or equal to minimum length." - ); - } - - Ok(rule) - } -} - -pub fn declare_oxc_secret(meta: SecretRuleMeta) -> TokenStream { - let SecretRuleMeta { - // - struct_name, - message, - entropy, - min_len, - max_len, - } = meta; - - let rule_name = rule_name_converter().convert(struct_name.to_string()); - - let min_len_fn = min_len.map(|min_len| { - quote! { - #[inline] - fn min_len(&self) -> NonZeroU32 { - // SAFETY: #min_len is a valid value for NonZeroU32 - unsafe { NonZeroU32::new_unchecked(#min_len) } - } - } - }); - - let max_len_fn = max_len.map(|max_len| { - quote! { - #[inline] - fn max_len(&self) -> Option { - Some(unsafe { NonZeroU32::new_unchecked(#max_len) }) - } - } - }); - - let entropy_fn = entropy.map(|entropy| { - quote! { - #[inline] - fn min_entropy(&self) -> f32 { - #entropy - } - } - }); - - let output = quote! { - impl super::SecretScannerMeta for #struct_name { - #[inline] - fn rule_name(&self) -> std::borrow::Cow<'static, str> { - std::borrow::Cow::Borrowed(#rule_name) - } - - #[inline] - fn message(&self) -> std::borrow::Cow<'static, str> { - std::borrow::Cow::Borrowed(#message) - } - - #min_len_fn - - #max_len_fn - - #entropy_fn - } - }; - - TokenStream::from(output) -} diff --git a/crates/oxc_macros/src/lib.rs b/crates/oxc_macros/src/lib.rs index 5177c42a3a5d6..7953194a1a8f8 100644 --- a/crates/oxc_macros/src/lib.rs +++ b/crates/oxc_macros/src/lib.rs @@ -3,11 +3,8 @@ use proc_macro::TokenStream; use syn::parse_macro_input; -#[macro_use] -mod util; mod declare_all_lint_rules; mod declare_oxc_lint; -mod declare_oxc_secret; /// Macro used to declare an oxc lint rule /// @@ -110,65 +107,3 @@ pub fn declare_all_lint_rules(input: TokenStream) -> TokenStream { let metadata = parse_macro_input!(input as declare_all_lint_rules::AllLintRulesMeta); declare_all_lint_rules::declare_all_lint_rules(metadata) } - -/// Declare a secret scanner for `oxc-security/api-keys`. -/// -/// Scanner definitions are composed of: -/// 1. The scanner struct name, -/// 2. A message displayed in diagnostics, -/// 3. A set of `key = value` config pairs. -/// -/// # Pre-Verify Configuration -/// -/// These configs filter secret candidates before `verify` is ever called. This -/// is for performance reasons, as many `verify` implementations use expensive -/// checks (such as regular expression matching). All configs are optional. -/// -/// The following config key/value pairs are available: -/// -/// ## `entropy` ([`f32`]) -/// Minimum [Shannon -/// entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory)) a -/// candidate must have to be considered a violation. Must be a positive, -/// non-zero `f32`. Defaults to `0.5`. -/// -/// ## `min_len` ([`NonZeroU32`]) -/// Minimum length a key candidate must have to be considered a violation. This -/// is a `u32` greater than 0. Defaults to `8`. -/// -/// ## `max_len` ([`Option`]) -/// Maximum length a key candidate must have to be considered a violation. This -/// is a `u32` greater than 0. By default, no maximum is enforced ([`None`]). -/// -/// # Example -/// ``` -/// use oxc_macros::declare_oxc_secret; -/// use super::SecretScanner; -/// -/// #[derive(Debug, Default, Clone)] -/// pub struct AwsAccessKeyId; -/// -/// declare_oxc_secret! { -/// AwsAccessKeyId, -/// "Detected an AWS Access Key ID, which can be used to access AWS resources.", -/// entropy = 4.0, -/// min_len = 20, -/// max_len = 20, -/// } -/// -/// impl SecretScanner for AwsAccessKeyId { -/// fn detect(&self, candidate: &Secret<'_>) -> bool { -/// // Look for AKIA, ASIA, or AIDA -/// ["AKIA", "ASIA", "AIDA"].iter().any(|prefix| candidate.starts_with(prefix)) && -/// !candidate.cha -/// } -/// } -/// -/// ``` -/// -/// [`NonZeroU32`]: std::num::NonZeroU32 -#[proc_macro] -pub fn declare_oxc_secret(input: TokenStream) -> TokenStream { - let metadata = parse_macro_input!(input as declare_oxc_secret::SecretRuleMeta); - declare_oxc_secret::declare_oxc_secret(metadata) -} diff --git a/crates/oxc_macros/src/util.rs b/crates/oxc_macros/src/util.rs deleted file mode 100644 index 3b4f52c4a6adb..0000000000000 --- a/crates/oxc_macros/src/util.rs +++ /dev/null @@ -1,34 +0,0 @@ -use syn::{parse::ParseStream, Result, Token}; - -/// Checks if `cond` is `true`, returning [`Err(syn::Error)`] with `msg` if it's not. -/// ## Example -/// ```ignore -/// use syn::{parse::Parse, LitStr}; -/// -/// struct Foo(LitStr); -/// -/// impl Parse for Foo { -/// fn parse(input: ParseStream) -> Result { -/// let s = input.parse::()?; -/// parse_assert!(s.value() == "foo", s, "Expected 'foo'"); -/// Ok(Foo(s)) -/// } -/// } -/// ``` -macro_rules! parse_assert { - ($cond:expr, $toks:expr, $msg:expr) => { - if !($cond) { - return Err(syn::Error::new_spanned($toks, $msg)); - } - }; -} -pub(crate) use parse_assert; - -/// Consume a comma token if it's present, noop otherwise -#[allow(clippy::trivially_copy_pass_by_ref)] -pub(crate) fn eat_comma(input: &ParseStream) -> Result<()> { - if input.peek(Token!(,)) { - input.parse::()?; - } - Ok(()) -} diff --git a/justfile b/justfile index 0c28a998767d7..44016f3b4bb5a 100755 --- a/justfile +++ b/justfile @@ -199,9 +199,6 @@ new-promise-rule name: new-vitest-rule name: cargo run -p rulegen {{name}} vitest -new-security-rule name: - cargo run -p rulegen {{name}} security - [unix] clone-submodule dir url sha: cd {{dir}} || git init {{dir}} diff --git a/tasks/rulegen/src/main.rs b/tasks/rulegen/src/main.rs index 7d7eecd10ac5c..57c03810f0dfc 100644 --- a/tasks/rulegen/src/main.rs +++ b/tasks/rulegen/src/main.rs @@ -597,7 +597,6 @@ pub enum RuleKind { Node, Promise, Vitest, - Security, } impl RuleKind { @@ -616,7 +615,6 @@ impl RuleKind { "n" => Self::Node, "promise" => Self::Promise, "vitest" => Self::Vitest, - "security" => Self::Security, _ => Self::ESLint, } } @@ -639,7 +637,6 @@ impl Display for RuleKind { Self::Node => write!(f, "eslint-plugin-n"), Self::Promise => write!(f, "eslint-plugin-promise"), Self::Vitest => write!(f, "eslint-plugin-vitest"), - Self::Security => write!(f, "security"), } } } @@ -668,7 +665,7 @@ fn main() { RuleKind::Node => format!("{NODE_TEST_PATH}/{kebab_rule_name}.js"), RuleKind::Promise => format!("{PROMISE_TEST_PATH}/{kebab_rule_name}.js"), RuleKind::Vitest => format!("{VITEST_TEST_PATH}/{kebab_rule_name}.test.ts"), - RuleKind::Oxc | RuleKind::Security => String::new(), + RuleKind::Oxc => String::new(), }; let language = match rule_kind { RuleKind::Typescript | RuleKind::Oxc => "ts", @@ -791,7 +788,6 @@ fn add_rules_entry(ctx: &Context, rule_kind: RuleKind) -> Result<(), Box "promise", RuleKind::Vitest => "vitest", RuleKind::Node => "node", - RuleKind::Security => "security", }; let mod_def = format!("mod {mod_name}"); let Some(mod_start) = rules.find(&mod_def) else { diff --git a/tasks/rulegen/src/template.rs b/tasks/rulegen/src/template.rs index 6adc34937fd8d..939b9e99f4f98 100644 --- a/tasks/rulegen/src/template.rs +++ b/tasks/rulegen/src/template.rs @@ -44,7 +44,6 @@ impl<'a> Template<'a> { RuleKind::Node => Path::new("crates/oxc_linter/src/rules/node"), RuleKind::Promise => Path::new("crates/oxc_linter/src/rules/promise"), RuleKind::Vitest => Path::new("crates/oxc_linter/src/rules/vitest"), - RuleKind::Security => Path::new("crates/oxc_linter/src/rules/security"), }; std::fs::create_dir_all(path)?; diff --git a/tasks/website/src/linter/snapshots/cli.snap b/tasks/website/src/linter/snapshots/cli.snap index e7653edd16d2a..d2766eb69aee8 100644 --- a/tasks/website/src/linter/snapshots/cli.snap +++ b/tasks/website/src/linter/snapshots/cli.snap @@ -70,8 +70,6 @@ Arguments: Enable the promise plugin and detect promise usage problems - **` --node-plugin`** — Enable the node plugin and detect node usage problems -- **` --security-plugin`** — - Enable the security plugin and detect security problems diff --git a/tasks/website/src/linter/snapshots/cli_terminal.snap b/tasks/website/src/linter/snapshots/cli_terminal.snap index 3d55cd84e0835..fef1bf28b61fc 100644 --- a/tasks/website/src/linter/snapshots/cli_terminal.snap +++ b/tasks/website/src/linter/snapshots/cli_terminal.snap @@ -44,7 +44,6 @@ Enable Plugins problems --promise-plugin Enable the promise plugin and detect promise usage problems --node-plugin Enable the node plugin and detect node usage problems - --security-plugin Enable the security plugin and detect security problems Fix Problems --fix Fix as many issues as possible. Only unfixed issues are reported in