use super::*;
pub(crate) const METADATA_RULES: &[RuleDefinition] = &[
rule_definition!(
"modernisation.manual-is-empty",
"Manual is_empty check",
Pillar::Modernisation,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags `len() == 0` and `len() != 0` comparisons that should use `is_empty()`.",
),
rule_definition!(
"modernisation.manual-contains",
"Manual contains check",
Pillar::Modernisation,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags `iter().any(|x| *x == y)` patterns that should use `.contains(&y)`.",
),
rule_definition!(
"modernisation.manual-strip-prefix",
"Manual strip_prefix",
Pillar::Modernisation,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags `if s.starts_with(p) { &s[p.len()..] }` shapes that should use `strip_prefix`.",
),
rule_definition!(
"modernisation.manual-unwrap-or-default",
"Manual unwrap_or_default",
Pillar::Modernisation,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags `match opt { Some(v) => v, None => Default::default() }` shapes that should use `unwrap_or_default()`.",
),
rule_definition!(
"modernisation.question-mark-candidate",
"Manual question-mark candidate",
Pillar::Modernisation,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags manual `match`/`if let Err` Result-propagation shapes that should use `?`.",
),
];
const NAMING_GENERIC_FUNCTION_OPTIONS: &[OptionDefinition] = &[OptionDefinition {
name: "extraGenericNames",
description: "Additional generic function names rejected by naming.generic-function.",
value_kind: OptionValueKind::StringArray,
}];
const NAMING_BOOLEAN_PREFIX_OPTIONS: &[OptionDefinition] = &[OptionDefinition {
name: "predicatePrefixes",
description: "Additional predicate prefixes accepted by naming.boolean-prefix.",
value_kind: OptionValueKind::StringArray,
}];
const NAMING_PLACEHOLDER_OPTIONS: &[OptionDefinition] = &[OptionDefinition {
name: "extraPlaceholders",
description: "Additional placeholder identifiers rejected by naming.placeholder-identifier.",
value_kind: OptionValueKind::StringArray,
}];
pub(crate) const NAMING_RULES: &[RuleDefinition] = &[
RuleDefinition {
id: "naming.generic-function",
name: "Generic function name",
pillar: Pillar::Naming,
tier: "v0.1",
kind: RuleKind::Rust,
default_severity: Severity::Advisory,
confidence: Confidence::High,
threshold: None,
options: NAMING_GENERIC_FUNCTION_OPTIONS,
default_enabled: true,
description: "Flags function names that are too generic to explain intent.",
false_positive_shapes: &[
FalsePositiveShape {
shape: "Bridge functions whose name is dictated by an external macro (`#[wasm_bindgen]`, FFI signatures, JSON-RPC handlers).",
mitigation: "Add the host path to `paths.ignore` in `.gruff-rs.yaml`, or set `rules.naming.generic-function.enabled: false` on the affected scope (`extraGenericNames` extends the blocklist, not the allow-list).",
},
],
related_rules: &[
"naming.boolean-prefix",
"naming.placeholder-identifier",
"naming.short-variable",
],
},
RuleDefinition {
id: "naming.boolean-prefix",
name: "Boolean predicate prefix",
pillar: Pillar::Naming,
tier: "v0.1",
kind: RuleKind::Rust,
default_severity: Severity::Advisory,
confidence: Confidence::High,
threshold: None,
options: NAMING_BOOLEAN_PREFIX_OPTIONS,
default_enabled: true,
description: "Flags bool-returning functions whose names do not read like predicates.",
false_positive_shapes: &[
FalsePositiveShape {
shape: "Domain verbs that are inherently truthful (e.g. `validate`, `assert_`, `ensure_`).",
mitigation: "Add the prefix to `rules.naming.boolean-prefix.options.predicatePrefixes` in `.gruff-rs.yaml`.",
},
],
related_rules: &["naming.generic-function", "naming.placeholder-identifier"],
},
RuleDefinition {
id: "naming.placeholder-identifier",
name: "Placeholder identifier",
pillar: Pillar::Naming,
tier: "v0.1",
kind: RuleKind::Rust,
default_severity: Severity::Advisory,
confidence: Confidence::Medium,
threshold: None,
options: NAMING_PLACEHOLDER_OPTIONS,
default_enabled: true,
description: "Flags placeholder identifiers such as foo, bar, baz, and qux.",
false_positive_shapes: &[
FalsePositiveShape {
shape: "Conventional loop variables in test fixtures or generated examples (`for foo in items`).",
mitigation: "Add the host path to `paths.ignore` in `.gruff-rs.yaml` (`extraPlaceholders` extends the blocklist, not the allow-list).",
},
],
related_rules: &["naming.generic-function", "naming.short-variable"],
},
rule_definition!(
"naming.short-variable",
"Short variable name",
Pillar::Naming,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags very short local variable names outside accepted abbreviations.",
false_positives: &[
FalsePositiveShape {
shape: "Domain abbreviations specific to the project (e.g. `aws`, `kms`, `ssn`).",
mitigation: "Append the token to `allowlists.acceptedAbbreviations` in `.gruff-rs.yaml`.",
},
],
related: &["naming.generic-function", "naming.placeholder-identifier"],
),
rule_definition!(
"naming.identifier-shadow",
"Identifier shadow",
Pillar::Naming,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags `let X = X(...)` bindings that shadow a same-file free function.",
),
];
pub(crate) const PERFORMANCE_AND_SECURITY_RULES: &[RuleDefinition] = &[
rule_definition!(
"ci.github-event-shell-interpolation",
"GitHub event shell interpolation",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::High,
None,
"Flags GitHub event values interpolated directly into workflow shell steps.",
),
rule_definition!(
"security.github-actions-broad-permissions",
"GitHub Actions broad permissions",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::Medium,
None,
"Flags workflow permissions that grant broad write access.",
),
rule_definition!(
"security.github-actions-pull-request-target",
"GitHub Actions pull_request_target",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::Medium,
None,
"Flags pull_request_target workflows for manual secret and checkout review.",
),
rule_definition!(
"security.github-actions-remote-shell",
"GitHub Actions remote shell",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::High,
None,
"Flags workflow shell steps that pipe remote downloads into an interpreter.",
),
rule_definition!(
"security.github-actions-secrets-in-pr",
"GitHub Actions secrets in pull request",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::Medium,
None,
"Flags pull request workflows that reference repository secrets.",
),
rule_definition!(
"security.github-actions-unpinned-action",
"GitHub Actions unpinned action",
Pillar::Security,
RuleKind::Text,
Severity::Warning,
Confidence::Medium,
None,
"Flags third-party workflow actions that are not pinned to a full commit SHA.",
),
rule_definition!(
"performance.clone-in-loop",
"Clone in loop",
Pillar::Maintainability,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags clone calls inside loop bodies as allocation hot spot candidates.",
),
rule_definition!(
"performance.format-in-loop",
"Format in loop",
Pillar::Maintainability,
RuleKind::Rust,
Severity::Advisory,
Confidence::Medium,
None,
"Flags format! calls inside loop bodies as allocation hot spot candidates.",
),
rule_definition!(
"performance.regex-in-loop",
"Regex construction in loop",
Pillar::Maintainability,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags Regex::new calls inside loop bodies.",
),
rule_definition!(
"security.process-command",
"Process command execution",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags process command construction for manual argument validation.",
false_positives: &[
FalsePositiveShape {
shape: "Test fixtures or builders that construct commands with fully literal arguments and never pass user input.",
mitigation: "Add an `exclude:` entry for that path in `.gruff-rs.yaml` with a documented reason, or refactor the call into a helper that the rule's path-aware skip recognises (`tests/`, `fixtures/`).",
},
],
related: &["security.insecure-rng-for-secrets", "sensitive-data.api-key"],
),
rule_definition!(
"security.insecure-rng-for-secrets",
"Insecure RNG for secret material",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags non-cryptographic rand calls inside secret-like generation functions.",
),
rule_definition!(
"security.sql-dynamic-query",
"Dynamic SQL query argument",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags SQL-keyword-bearing dynamic query arguments such as query(format!(...)).",
false_positives: &[
FalsePositiveShape {
shape: "SQL-keyword-bearing non-SQL DSL text passed to a method named query, execute, or prepare.",
mitigation: "Rename the wrapper method if possible, or add an `exclude:` entry for the reviewed path and message.",
},
FalsePositiveShape {
shape: "Locally bounded table, schema, or prefix interpolation that cannot use bind parameters because SQL identifiers are dynamic.",
mitigation: "Prefer a static statement per identifier, prove the identifier comes from a literal/const allowlist in code review, or add a documented `exclude:` entry for that path.",
},
],
related: &[],
),
rule_definition!(
"security.tls-verification-disabled",
"TLS verification disabled",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags explicit TLS certificate or hostname verification bypasses.",
),
rule_definition!(
"security.unsafe-block",
"Unsafe block",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags unsafe blocks without a nearby SAFETY rationale.",
),
rule_definition!(
"security.weak-crypto",
"Weak cryptographic primitive",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags explicit weak cryptographic primitive imports or constructors for review.",
),
rule_definition!(
"security.path-traversal-candidate",
"Path traversal candidate",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags filesystem path construction where externally-derived input is joined without normalisation.",
false_positives: &[
FalsePositiveShape {
shape: "Custom domain types that expose a `.join(...)` method unrelated to filesystem paths.",
mitigation: "The rule requires filesystem receiver evidence for `.join(...)` calls, such as a path-typed receiver or base-directory naming. Keep non-filesystem receivers domain-specific rather than naming them like roots or directories.",
},
FalsePositiveShape {
shape: "Segments sanitized before joining by removing traversal and both path separators.",
mitigation: "Use a visible sanitizer or local replacement chain that removes `..`, `/`, and `\\`; generic names like `key` or `value` remain reportable unless the defense is explicit.",
},
],
related: &["security.process-command", "security.sql-dynamic-query"],
),
rule_definition!(
"security.ssrf-candidate",
"SSRF candidate",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags request URLs derived from local input without nearby allow-list evidence.",
),
rule_definition!(
"security.template-injection-xss",
"Template injection or XSS candidate",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags HTML/template output that includes request-derived values without local escaping evidence.",
),
rule_definition!(
"security.hardcoded-bind-all-interfaces",
"Hardcoded bind to all interfaces",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags listener address literals like `0.0.0.0` or `[::]` outside test infrastructure.",
),
rule_definition!(
"security.unsafe-deserialization",
"Unsafe deserialization candidate",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags YAML or binary deserialization of data derived from local input.",
),
rule_definition!(
"security.xxe-candidate",
"XXE candidate",
Pillar::Security,
RuleKind::Rust,
Severity::Warning,
Confidence::Medium,
None,
"Flags XML parser options that enable external entity or DTD resolution.",
),
];
pub(crate) const SENSITIVE_DATA_RULES: &[RuleDefinition] = &[
rule_definition!(
"sensitive-data.api-key-pattern",
"API key pattern",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags common API key patterns.",
),
rule_definition!(
"sensitive-data.aws-access-key",
"AWS access key",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags AWS access key patterns.",
),
rule_definition!(
"sensitive-data.database-url-password",
"Database URL password",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags database URLs that appear to include passwords.",
),
rule_definition!(
"sensitive-data.gcp-service-account-key",
"GCP service account key",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags committed GCP service-account private key material.",
),
rule_definition!(
"sensitive-data.hardcoded-env-value",
"Hardcoded environment-style secret",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags secret-like KEY=value literals committed in source or config.",
),
rule_definition!(
"sensitive-data.high-entropy-string",
"High entropy string",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::Medium,
None,
"Flags long string literals that look like generated secrets while skipping known structured non-secret values.",
false_positives: &[
FalsePositiveShape {
shape: "Zero-separator CamelCase or mixed-case identifiers that are high entropy but not secrets.",
mitigation: "Prefer a separator-bearing identifier when practical, or add the deterministic redacted preview to `secret_previews` after review.",
},
FalsePositiveShape {
shape: "Manifest checksum or signature fields whose value shape alone is indistinguishable from secret material.",
mitigation: "Keep package integrity prefixes such as `sha1-`/`sha512-` where possible; otherwise document the field and use `secret_previews` for the reviewed value.",
},
],
related: &["sensitive-data.api-key-pattern", "sensitive-data.jwt-token"],
),
rule_definition!(
"sensitive-data.jwt-token",
"JWT token",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags JWT-looking token strings.",
),
rule_definition!(
"sensitive-data.private-key",
"Private key block",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags private key block markers.",
),
rule_definition!(
"sensitive-data.url-embedded-credentials",
"URL embedded credentials",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags HTTP(S) URLs that include embedded username and password credentials.",
),
rule_definition!(
"sensitive-data.pii-test-fixture",
"PII in test fixture",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags realistic emails, SSN-shaped strings, or US phone numbers in fixture or sample files.",
),
rule_definition!(
"sensitive-data.phi-pattern",
"Protected health identifier",
Pillar::SensitiveData,
RuleKind::Text,
Severity::Error,
Confidence::High,
None,
"Flags SSN, MRN, and Medicare-style health identifiers with redacted previews.",
),
];
pub(crate) const SIZE_RULES: &[RuleDefinition] = &[
rule_definition!(
"size.file-length",
"File length",
Pillar::Size,
RuleKind::Text,
Severity::Warning,
Confidence::High,
FILE_LENGTH_THRESHOLD,
"Flags files over the configured line-count threshold.",
),
rule_definition!(
"size.function-length",
"Function length",
Pillar::Size,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
FUNCTION_LENGTH_THRESHOLD,
"Flags functions over the configured line-count threshold.",
false_positives: &[
FalsePositiveShape {
shape: "Functions whose body is a single declarative literal (large match table, builder chain).",
mitigation: "Increase `rules.size.function-length.threshold` in `.gruff-rs.yaml`, or refactor the literal into a `const` table.",
},
],
related: &[
"size.parameter-count",
],
),
rule_definition!(
"size.parameter-count",
"Parameter count",
Pillar::Size,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
PARAMETER_COUNT_THRESHOLD,
"Flags functions with too many parameters.",
false_positives: &[
FalsePositiveShape {
shape: "Public API entry points whose signature mirrors an external contract (FFI, JSON-RPC).",
mitigation: "Increase `rules.size.parameter-count.threshold` in `.gruff-rs.yaml`, or wrap the parameter set in a builder struct.",
},
],
related: &["size.function-length", "complexity.cognitive"],
),
];
pub(crate) const TEST_QUALITY_RULES: &[RuleDefinition] = &[
rule_definition!(
"test-quality.conditional-logic",
"Conditional logic in test",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags tests that contain conditional logic.",
),
rule_definition!(
"test-quality.ignored-without-reason",
"Ignored test without reason",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags ignored tests that do not explain why they are skipped.",
),
rule_definition!(
"test-quality.long-test",
"Long test",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
TEST_LONG_THRESHOLD,
"Flags long test functions that are harder to scan and maintain.",
),
rule_definition!(
"test-quality.sleep-in-test",
"Sleep in test",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags tests that sleep instead of synchronizing on behavior.",
),
rule_definition!(
"test-quality.trivial-assertion",
"Trivial assertion",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Warning,
Confidence::High,
None,
"Flags assertions that prove a value the code already fixes instead of behavior: literal tautologies (`assert!(true)`, `assert_eq!(1, 1)`) and an immutable literal binding asserted against its own value (`let x = 5; assert_eq!(x, 5)`).",
false_positives: &[
FalsePositiveShape {
shape: "Trait or auto-trait witness idioms (`fn _assert_send<T: Send>() {}`, `let _: &dyn Trait = &value;`) that deliberately assert a static fact at compile time.",
mitigation: "Not flagged: these carry no runtime assertion macro. Keep them as compile-time checks; if a wrapper macro trips the rule, add an `exclude:` entry in `.gruff-rs.yaml` with a documented reason.",
},
FalsePositiveShape {
shape: "Contract assertions where the value is the behavior under test: a `Default`/constructor value (`assert_eq!(Config::default().retries, 3)`), an ABI guard (`assert_eq!(size_of::<T>(), 8)`), a fallible result (`assert!(parse(s).is_ok())`), or a value computed into a different binding (`let n = seed * 2; assert_eq!(n, 10)`).",
mitigation: "Not flagged: the asserted value is produced by a call or computation, not bound directly to the matching literal. Keep them as regression guards.",
},
],
related: &[],
),
rule_definition!(
"test-quality.unwrap-in-test",
"Unwrap in test",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags unwrap calls in tests.",
),
rule_definition!(
"test-quality.should-panic-without-expected",
"Should-panic without expected message",
Pillar::TestQuality,
RuleKind::Rust,
Severity::Advisory,
Confidence::High,
None,
"Flags `#[should_panic]` attributes without an `expected = \"...\"` clause.",
),
];