use bonsai_lang_api::{DeclKind, StaticScalarValue, Visibility};
use serde::{de, Deserialize, Deserializer, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum RuleKind {
Source,
Sink,
Sanitizer,
Typing,
}
impl Default for RuleKind {
fn default() -> Self {
Self::Source
}
}
impl RuleKind {
#[must_use]
pub fn dir_name(self) -> &'static str {
match self {
Self::Source => "sources",
Self::Sink => "sinks",
Self::Sanitizer => "sanitizers",
Self::Typing => "typing",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TrustClass {
Remote,
Local,
Service,
Ipc,
Database,
Library,
Config,
Physical,
}
impl TrustClass {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Remote => "remote",
Self::Local => "local",
Self::Service => "service",
Self::Ipc => "ipc",
Self::Database => "database",
Self::Library => "library",
Self::Config => "config",
Self::Physical => "physical",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PayloadType {
Query,
Path,
Header,
Cookie,
Form,
Multipart,
File,
Json,
Xml,
Yaml,
Graphql,
Jwt,
OauthToken,
Protobuf,
Msgpack,
Csv,
Text,
Binary,
Url,
Hostname,
Ip,
Sql,
Template,
Event,
QueueMessage,
PubsubMessage,
DbRow,
ConfigValue,
SensorFrame,
HardwareRegister,
Html,
}
impl PayloadType {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Query => "query",
Self::Path => "path",
Self::Header => "header",
Self::Cookie => "cookie",
Self::Form => "form",
Self::Multipart => "multipart",
Self::File => "file",
Self::Json => "json",
Self::Xml => "xml",
Self::Yaml => "yaml",
Self::Graphql => "graphql",
Self::Jwt => "jwt",
Self::OauthToken => "oauth-token",
Self::Protobuf => "protobuf",
Self::Msgpack => "msgpack",
Self::Csv => "csv",
Self::Text => "text",
Self::Binary => "binary",
Self::Url => "url",
Self::Hostname => "hostname",
Self::Ip => "ip",
Self::Sql => "sql",
Self::Template => "template",
Self::Event => "event",
Self::QueueMessage => "queue-message",
Self::PubsubMessage => "pubsub-message",
Self::DbRow => "db-row",
Self::ConfigValue => "config-value",
Self::SensorFrame => "sensor-frame",
Self::HardwareRegister => "hardware-register",
Self::Html => "html",
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MatchKind {
Call,
Type,
Read,
Write,
New,
Return,
Param,
Missing,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuleTarget {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribute: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_name_in: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_name_not_in: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub annotation: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_call: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub in_class: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub in_method: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub in_method_prefix: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_index_in: Vec<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_type_in: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_count_in: Vec<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub base_param_index_in: Vec<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub receiver_type_in: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub decl_kind_in: Vec<DeclKind>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub visibility_in: Vec<Visibility>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub call_kind_in: Vec<bonsai_lang_api::CallKind>,
}
impl RuleTarget {
pub fn is_empty(&self) -> bool {
self.name.is_none()
&& self.attribute.is_none()
&& self.regex.is_none()
&& self.annotation.is_none()
&& self.default_call.is_none()
&& self.base_name_in.is_empty()
&& self.base_name_not_in.is_empty()
&& self.in_class.is_empty()
&& self.in_method.is_empty()
&& self.in_method_prefix.is_empty()
&& self.param_index_in.is_empty()
&& self.param_type_in.is_empty()
&& self.param_count_in.is_empty()
&& self.base_param_index_in.is_empty()
&& self.receiver_type_in.is_empty()
&& self.decl_kind_in.is_empty()
&& self.visibility_in.is_empty()
&& self.call_kind_in.is_empty()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MatchSpec {
pub kind: MatchKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub callee: Option<RuleTarget>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target: Option<RuleTarget>,
#[serde(default, skip_serializing_if = "is_zero_u32")]
pub search_depth: u32,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_zero_u32(value: &u32) -> bool {
*value == 0
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaintSemantics {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clean_output_overwrite: Option<CleanOutputOverwriteSemantics>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_output_args: Vec<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_callback_args: Vec<SourceCallbackArgSemantics>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub call_result_passthrough_args: Vec<usize>,
#[serde(default, skip_serializing_if = "is_false")]
pub call_result_passthrough_receiver: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub output_arg_flows: Vec<OutputArgFlowSemantics>,
#[serde(default, skip_serializing_if = "is_false")]
pub taint_receiver_from_args: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum LifecycleBindingTarget {
Receiver,
Argument { index: u32 },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LifecycleTransitionSemantics {
pub state: String,
pub binding: LifecycleBindingTarget,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum FlowClass {
ProcessInput,
HttpInput,
EnvironmentInput,
ProcessExecution,
BrowserOutput,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum MatchOrigin {
#[default]
Rulepack,
InferredUnreferencedParameter,
InferredFrameworkParameter,
InferredClassField,
Pattern,
EngineSanitizer,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum GuardProfile {
#[serde(alias = "python-path-containment")]
CanonicalPathContainment,
#[serde(
rename = "path-consumer-containment",
alias = "python-path-consumer-containment"
)]
PathConsumerContainment,
RelativePathContainment,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CompilerGuardSemantics {
pub capability: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_evidence: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub forbidden_evidence: Vec<String>,
pub sanitizer_tag: String,
pub category: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SanitizerGuardSemantics {
#[serde(default)]
pub use_receiver: bool,
#[serde(default)]
pub all_arguments: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub argument_indices: Vec<usize>,
#[serde(default)]
pub require_terminal_rejection: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PathContainmentGuardSemantics {
pub canonicalizer: RuleTarget,
pub containment_check: RuleTarget,
pub sink_base_arg_index: usize,
pub boundary_places: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PathConsumerContainmentGuardSemantics {
pub canonicalizer: RuleTarget,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_canonicalizer: Option<RuleTarget>,
pub path_constructor: RuleTarget,
#[serde(default)]
pub path_constructor_base_from_receiver: bool,
pub containment_check: RuleTarget,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub static_base_factories: Vec<RuleTarget>,
pub sink_path_arg_index: usize,
pub path_constructor_base_arg_index: usize,
#[serde(default)]
pub containment_check_is_segment_aware: bool,
pub boundary_places: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RelativePathContainmentGuardSemantics {
pub candidate_canonicalizer: RuleTarget,
pub base_canonicalizer: RuleTarget,
pub relative_path: RuleTarget,
pub relative_path_result_index: usize,
pub relative_base_arg_index: usize,
pub relative_candidate_arg_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub guarded_path_arg_index: Option<usize>,
pub rejection_check: RuleTarget,
pub rejection_check_arg_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rejection_prefix_arg_index: Option<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rejection_boundary_places: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rejection_boundary_wrappers: Vec<RuleTarget>,
pub rejected_exact_values: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ParameterizedQuerySemantics {
pub query_arg_index: usize,
pub bindings_arg_index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct NoSqlFilterSemantics {
pub filter_arg_index: usize,
pub literal_value_operators: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub safe_scalar_runtime_types: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub safe_scalar_compiler_types: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub safe_scalar_source_rules: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DynamicKeyDenylistGuardSemantics {
pub collection_constructor: RuleTarget,
pub membership_check: RuleTarget,
pub membership_subject_arg_index: usize,
pub collection_values_arg_index: usize,
pub rejected_exact_values: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sink_key_argument_index: Option<usize>,
#[serde(default)]
pub require_recursive_filter: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filtered_value_argument_index: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReceiverFactoryGuardSemantics {
pub factories: Vec<RuleTarget>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_nested_factories: Vec<RuleTarget>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequiredCallArgumentSemantics {
pub index: usize,
#[serde(default)]
pub require_static_value: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accepted_places: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accepted_static_values: Vec<StaticScalarValue>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequiredReceiverCallSemantics {
pub call: RuleTarget,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub identity_argument_indices: Vec<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_arguments: Vec<RequiredCallArgumentSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReceiverConfigurationGuardSemantics {
pub required_calls: Vec<RequiredReceiverCallSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfiguredArgumentReceiverGuardSemantics {
pub sink_argument_index: usize,
pub wrapper_factory: RuleTarget,
pub configured_receiver_argument_index: usize,
pub provider_factory: RuleTarget,
pub required_calls: Vec<RequiredReceiverCallSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequiredNamedArgumentSemantics {
pub name: String,
pub value: StaticScalarValue,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfiguredArgumentFactoryGuardSemantics {
pub sink_argument_index: usize,
pub factory: RuleTarget,
pub required_named_arguments: Vec<RequiredNamedArgumentSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequiredAggregateFieldSemantics {
pub path: Vec<String>,
pub value: StaticScalarValue,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReceiverFactoryArgumentFieldsSpec {
pub factory: RuleTarget,
pub configuration_argument_index: usize,
pub required_fields: Vec<RequiredAggregateFieldSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConfiguredCallArgumentGuardSemantics {
pub configuration_argument_index: usize,
pub guarded_value_argument_indices: Vec<usize>,
pub required_fields: Vec<RequiredAggregateFieldSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExactStringMapping {
pub input: String,
pub output: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CharacterEscapeSemantics {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub value_arg_indices: Vec<usize>,
pub required_mappings: Vec<ExactStringMapping>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CharacterConstraintSemantics {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_excluded_characters: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_mappings: Vec<ExactStringMapping>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required_enclosing_literal_delimiter: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accepted_providers: Vec<CharacterConstraintProviderSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CharacterConstraintProviderSemantics {
pub factory: RuleTarget,
pub operation: RuleTarget,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SameOriginPathConstraintSemantics {
pub require_scheme_rejection: bool,
pub require_authority_rejection: bool,
pub require_absolute_path: bool,
pub require_scheme_relative_rejection: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub accepted_providers: Vec<RuleTarget>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sink_argument_index: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub static_context_argument: Option<StaticContextArgumentSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StaticContextArgumentSemantics {
pub index: usize,
pub accepted_renderings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum UrlGuardRootSemantics {
SinkReceiver,
SinkAssignmentTarget,
SinkArgumentParserInput {
sink_argument_index: usize,
parser_argument_index: usize,
},
SinkArgumentAccessor {
argument_index: usize,
accessor: Box<RuleTarget>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlComponentSemantics {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accessor: Option<RuleTarget>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlSchemeGuardSemantics {
pub component: UrlComponentSemantics,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comparison_predicate: Option<RuleTarget>,
pub allowed_values: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reconstructed_values: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlHostAllowlistSemantics {
pub component: UrlComponentSemantics,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub membership_predicate: Option<RuleTarget>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub static_collection_factories: Vec<RuleTarget>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlAddressParserSemantics {
pub target: RuleTarget,
pub argument_index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlDnsGuardSemantics {
pub resolver: RuleTarget,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub address_parser: Option<UrlAddressParserSemantics>,
pub private_address_predicates: Vec<RuleTarget>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum UrlRedirectGuardSemantics {
ReceiverFieldExactCallback {
field: String,
required_return_place: String,
},
PostSinkCall {
call: Box<RuleTarget>,
argument_index: usize,
required_value: StaticScalarValue,
},
CallArgumentFields {
argument_index: usize,
required_fields: Vec<RequiredAggregateFieldSemantics>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlNetworkGuardSemantics {
pub root: UrlGuardRootSemantics,
pub parser: RuleTarget,
pub scheme: UrlSchemeGuardSemantics,
pub host_allowlist: UrlHostAllowlistSemantics,
pub dns: UrlDnsGuardSemantics,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub redirect: Option<UrlRedirectGuardSemantics>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UrlReconstructionGuardSemantics {
pub sink_argument_index: usize,
pub parser: RuleTarget,
pub scheme: UrlSchemeGuardSemantics,
pub host_allowlist: UrlHostAllowlistSemantics,
pub path_component: UrlComponentSemantics,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_fallback: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub redirect: Option<UrlRedirectGuardSemantics>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_sink_named_arguments: Vec<RequiredNamedArgumentSemantics>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ContextFlowRole {
Producer,
Consumer,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ContextFlowSemantics {
pub channel: String,
pub role: ContextFlowRole,
pub value_label: String,
pub parameter_name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rewrite_source_rule_ids: Vec<String>,
#[serde(default)]
pub sanitized_rewrite_clears_channel: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum PostSinkPolicy {
PathConstructionContainment,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SanitizerAttachmentPolicy {
ReceiverFactoryLineage,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AnalysisSemantics {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub flow_classes: Vec<FlowClass>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suppress_inferred_sources: Option<bool>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub suppress_local_source_flow_classes: Vec<FlowClass>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_specificity_rank: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_reporting_rank: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sink_terminal_priority: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub non_taint_evaluation: Option<NonTaintEvaluation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allow_file_package_evidence: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub skip_call_package_gate: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub guard_profile: Option<GuardProfile>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compiler_guard: Option<CompilerGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sanitizer_guard: Option<SanitizerGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_containment_guard: Option<PathContainmentGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_consumer_containment_guard: Option<PathConsumerContainmentGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub relative_path_containment_guard: Option<RelativePathContainmentGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parameterized_query: Option<ParameterizedQuerySemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub nosql_filter: Option<NoSqlFilterSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dynamic_key_denylist_guard: Option<DynamicKeyDenylistGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub receiver_factory_guard: Option<ReceiverFactoryGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub receiver_configuration_guard: Option<ReceiverConfigurationGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub configured_argument_factory_guard: Option<ConfiguredArgumentFactoryGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub configured_argument_receiver_guard: Option<ConfiguredArgumentReceiverGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub configured_call_argument_guard: Option<ConfiguredCallArgumentGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub character_escape: Option<CharacterEscapeSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub character_constraint: Option<CharacterConstraintSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub same_origin_path_constraint: Option<SameOriginPathConstraintSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_network_guard: Option<UrlNetworkGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url_reconstruction_guard: Option<UrlReconstructionGuardSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_flow: Option<ContextFlowSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub post_sink_policy: Option<PostSinkPolicy>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sanitizer_attachment_policy: Option<SanitizerAttachmentPolicy>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub receiver_factory_lineage_builders: Vec<RuleTarget>,
}
impl AnalysisSemantics {
pub(crate) fn merge_overriding(&mut self, incoming: Self) {
let mut merged = incoming;
merged.inherit_missing(self);
*self = merged;
}
pub(crate) fn inherit_missing(&mut self, defaults: &Self) {
if self.flow_classes.is_empty() {
self.flow_classes.clone_from(&defaults.flow_classes);
}
if self.suppress_local_source_flow_classes.is_empty() {
self.suppress_local_source_flow_classes
.clone_from(&defaults.suppress_local_source_flow_classes);
}
if let (Some(current), Some(default)) = (
self.character_constraint.as_mut(),
defaults.character_constraint.as_ref(),
) {
if current.accepted_providers.is_empty() {
current.accepted_providers.clone_from(&default.accepted_providers);
}
}
if self.character_constraint.is_none() {
self.character_constraint
.clone_from(&defaults.character_constraint);
}
if let (Some(current), Some(default)) = (
self.same_origin_path_constraint.as_mut(),
defaults.same_origin_path_constraint.as_ref(),
) {
if current.accepted_providers.is_empty() {
current.accepted_providers.clone_from(&default.accepted_providers);
}
}
macro_rules! inherit_option {
($($field:ident),+ $(,)?) => {
$(if self.$field.is_none() { self.$field = defaults.$field.clone(); })+
};
}
inherit_option!(
source_specificity_rank,
source_reporting_rank,
suppress_inferred_sources,
sink_terminal_priority,
non_taint_evaluation,
allow_file_package_evidence,
skip_call_package_gate,
guard_profile,
compiler_guard,
sanitizer_guard,
path_containment_guard,
path_consumer_containment_guard,
relative_path_containment_guard,
parameterized_query,
nosql_filter,
dynamic_key_denylist_guard,
receiver_factory_guard,
receiver_configuration_guard,
configured_argument_factory_guard,
configured_argument_receiver_guard,
configured_call_argument_guard,
character_escape,
same_origin_path_constraint,
url_network_guard,
url_reconstruction_guard,
context_flow,
post_sink_policy,
sanitizer_attachment_policy,
);
if self.receiver_factory_lineage_builders.is_empty() {
self.receiver_factory_lineage_builders
.clone_from(&defaults.receiver_factory_lineage_builders);
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum NonTaintEvaluation {
Pattern,
Lifecycle,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SourceCallbackArgSemantics {
pub callback_arg_index: usize,
pub source_param_indices: Vec<usize>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CleanOutputOverwriteSemantics {
pub output_arg_index: usize,
pub value_start_arg_index: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OutputArgFlowSemantics {
pub output_arg_index: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value_start_arg_index: Option<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub value_arg_indices: Vec<usize>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConstraintKind {
ReceiverTypeIn {
receiver_type_in: Vec<String>,
},
ReceiverTypeNotIn {
receiver_type_not_in: Vec<String>,
},
ReceiverMatchesRegex {
receiver_matches_regex: String,
},
ReceiverNotMatchesRegex {
receiver_not_matches_regex: String,
},
UnlessPriorReceiverCall {
unless_prior_receiver_call: Box<UnlessPriorReceiverCallSpec>,
},
SecondArgEquals {
second_arg_equals: String,
},
ArgEquals {
arg_equals: ArgEqualsSpec,
},
KeywordArgEquals {
keyword_arg_equals: KeywordArgEqualsSpec,
},
ArgTainted {
arg_tainted: ArgTaintedSpec,
},
ReceiverTainted {
receiver_tainted: bool,
},
AnyArgTainted {
any_arg_tainted: bool,
},
ReceiverOriginCallbackParamReachesCall {
receiver_origin_callback_param_reaches_call: Box<ReceiverOriginCallbackParamReachesCallSpec>,
},
ReceiverFactoryArgumentFieldsEqual {
receiver_factory_argument_fields_equal: Box<ReceiverFactoryArgumentFieldsSpec>,
},
FormatArgIndex {
format_arg_index: u32,
},
Namespace {
namespace: String,
},
TopLevel {
top_level: bool,
},
ArgCount {
arg_count: u32,
},
MinArgs {
min_args: u32,
},
MaxArgs {
max_args: u32,
},
ArgMatchesRegex {
arg_matches_regex: ArgRegexSpec,
},
ArgNotMatchesRegex {
arg_not_matches_regex: ArgRegexSpec,
},
AnyArgMatchesRegex {
any_arg_matches_regex: String,
},
ArgValueNotAggregate {
arg_value_not_aggregate: u32,
},
ArgSequenceItemsEqual {
arg_sequence_items_equal: ArgSequenceItemsSpec,
},
SameReceiverCallCountAtLeast {
same_receiver_call_count_at_least: u32,
},
ArgLt {
arg_lt: ArgIntSpec,
},
ArgLe {
arg_le: ArgIntSpec,
},
ArgGt {
arg_gt: ArgIntSpec,
},
ArgGe {
arg_ge: ArgIntSpec,
},
RequiresRuntimeType {
requires_runtime_type: RuntimeTypeSpec,
},
EnclosingDecoratorIn {
enclosing_decorator_in: Vec<String>,
},
EnclosingModifierIn {
enclosing_modifier_in: Vec<String>,
},
SinkTagIn {
sink_tag_in: Vec<String>,
},
MustAlias {
must_alias: MustAliasSpec,
},
RequiresState {
requires_state: RequiresStateSpec,
},
}
impl ConstraintKind {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::ReceiverTypeIn { .. } => "receiver_type_in",
Self::ReceiverTypeNotIn { .. } => "receiver_type_not_in",
Self::ReceiverMatchesRegex { .. } => "receiver_matches_regex",
Self::ReceiverNotMatchesRegex { .. } => "receiver_not_matches_regex",
Self::UnlessPriorReceiverCall { .. } => "unless_prior_receiver_call",
Self::SecondArgEquals { .. } => "second_arg_equals",
Self::ArgEquals { .. } => "arg_equals",
Self::KeywordArgEquals { .. } => "keyword_arg_equals",
Self::ArgTainted { .. } => "arg_tainted",
Self::ReceiverTainted { .. } => "receiver_tainted",
Self::AnyArgTainted { .. } => "any_arg_tainted",
Self::ReceiverOriginCallbackParamReachesCall { .. } => {
"receiver_origin_callback_param_reaches_call"
}
Self::ReceiverFactoryArgumentFieldsEqual { .. } => "receiver_factory_argument_fields_equal",
Self::FormatArgIndex { .. } => "format_arg_index",
Self::Namespace { .. } => "namespace",
Self::TopLevel { .. } => "top_level",
Self::ArgCount { .. } => "arg_count",
Self::MinArgs { .. } => "min_args",
Self::MaxArgs { .. } => "max_args",
Self::ArgMatchesRegex { .. } => "arg_matches_regex",
Self::ArgNotMatchesRegex { .. } => "arg_not_matches_regex",
Self::AnyArgMatchesRegex { .. } => "any_arg_matches_regex",
Self::ArgValueNotAggregate { .. } => "arg_value_not_aggregate",
Self::ArgSequenceItemsEqual { .. } => "arg_sequence_items_equal",
Self::SameReceiverCallCountAtLeast { .. } => "same_receiver_call_count_at_least",
Self::ArgLt { .. } => "arg_lt",
Self::ArgLe { .. } => "arg_le",
Self::ArgGt { .. } => "arg_gt",
Self::ArgGe { .. } => "arg_ge",
Self::RequiresRuntimeType { .. } => "requires_runtime_type",
Self::EnclosingDecoratorIn { .. } => "enclosing_decorator_in",
Self::EnclosingModifierIn { .. } => "enclosing_modifier_in",
Self::SinkTagIn { .. } => "sink_tag_in",
Self::MustAlias { .. } => "must_alias",
Self::RequiresState { .. } => "requires_state",
}
}
#[must_use]
pub fn is_discriminating(&self) -> bool {
matches!(
self,
Self::ArgTainted { .. }
| Self::ReceiverTainted { .. }
| Self::AnyArgTainted { .. }
| Self::ReceiverMatchesRegex { .. }
| Self::ReceiverNotMatchesRegex { .. }
| Self::UnlessPriorReceiverCall { .. }
| Self::ReceiverOriginCallbackParamReachesCall { .. }
| Self::SecondArgEquals { .. }
| Self::ArgEquals { .. }
| Self::KeywordArgEquals { .. }
| Self::ArgMatchesRegex { .. }
| Self::ArgNotMatchesRegex { .. }
| Self::AnyArgMatchesRegex { .. }
| Self::ArgValueNotAggregate { .. }
| Self::ArgSequenceItemsEqual { .. }
| Self::FormatArgIndex { .. }
| Self::ArgLt { .. }
| Self::ArgLe { .. }
| Self::ArgGt { .. }
| Self::ArgGe { .. }
| Self::RequiresRuntimeType { .. }
| Self::MustAlias { .. }
| Self::RequiresState { .. }
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UnlessPriorReceiverCallSpec {
pub call: RuleTarget,
pub static_string_args_regex: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ReceiverOriginCallbackParamReachesCallSpec {
pub receiver_factory: RuleTarget,
pub factory_tainted_arg_index: u32,
pub receiver_member: RuleTarget,
pub callback_param_index: u32,
pub callback_call: RuleTarget,
pub callback_call_arg_index: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MustAliasSpec {
pub source_arg: u32,
pub sink_arg: u32,
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RequiresStateSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<u32>,
pub expected: String,
}
impl<'de> Deserialize<'de> for RequiresStateSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Raw {
#[serde(default)]
name: Option<String>,
#[serde(default)]
index: Option<u32>,
expected: String,
}
let raw = Raw::deserialize(deserializer)?;
match (raw.name, raw.index) {
(Some(name), None) if !name.trim().is_empty() => Ok(Self {
name: Some(name),
index: None,
expected: raw.expected,
}),
(None, Some(index)) => Ok(Self {
name: None,
index: Some(index),
expected: raw.expected,
}),
_ => Err(de::Error::custom(
"requires_state must set exactly one of non-empty `name` or `index`",
)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArgIntSpec {
pub index: u32,
pub value: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeTypeSpec {
pub index: u32,
#[serde(rename = "type")]
pub type_name: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ArgTaintedSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub index: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kw: Option<String>,
}
impl<'de> Deserialize<'de> for ArgTaintedSpec {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Raw {
#[serde(default)]
index: Option<u32>,
#[serde(default)]
kw: Option<String>,
}
let raw = Raw::deserialize(deserializer)?;
match (raw.index, raw.kw) {
(Some(index), None) => Ok(Self {
index: Some(index),
kw: None,
}),
(None, Some(kw)) if !kw.trim().is_empty() => Ok(Self {
index: None,
kw: Some(kw),
}),
_ => Err(de::Error::custom(
"arg_tainted must set exactly one of `index` or non-empty `kw`",
)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArgEqualsSpec {
pub index: u32,
pub value: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KeywordArgEqualsSpec {
pub name: String,
pub value: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArgRegexSpec {
pub index: u32,
pub regex: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RequiredSequenceItemSpec {
pub index: usize,
pub accepted_values: Vec<StaticScalarValue>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ArgSequenceItemsSpec {
pub argument_index: usize,
pub items: Vec<RequiredSequenceItemSpec>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RuleConstraint(pub Vec<ConstraintKind>);
impl RuleConstraint {
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<'_, ConstraintKind> {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a RuleConstraint {
type Item = &'a ConstraintKind;
type IntoIter = std::slice::Iter<'a, ConstraintKind>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Clone, Debug, Default, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RuleMatchExample {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
pub code: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expect_match_text: Vec<String>,
#[serde(default, skip_serializing_if = "is_false")]
pub expect_no_match: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expect_no_match_text: Vec<String>,
}
#[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(value: &bool) -> bool {
!*value
}
impl<'de> Deserialize<'de> for RuleMatchExample {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Raw {
#[serde(default)]
name: Option<String>,
#[serde(default)]
path: Option<String>,
code: String,
#[serde(default)]
expect_match_text: Vec<String>,
#[serde(default)]
expect_no_match: bool,
#[serde(default)]
expect_no_match_text: Vec<String>,
}
let raw = Raw::deserialize(deserializer)?;
if raw.expect_no_match && !raw.expect_match_text.is_empty() {
return Err(de::Error::custom(
"match_examples entries cannot combine `expect_match_text` with `expect_no_match: true`",
));
}
if !raw.expect_no_match && !raw.expect_no_match_text.is_empty() {
return Err(de::Error::custom(
"`expect_no_match_text` requires `expect_no_match: true`",
));
}
Ok(Self {
name: raw.name,
path: raw.path,
code: raw.code,
expect_match_text: raw.expect_match_text,
expect_no_match: raw.expect_no_match,
expect_no_match_text: raw.expect_no_match_text,
})
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Info,
Low,
Medium,
High,
Critical,
}
impl Severity {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Info => "info",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
Self::Critical => "critical",
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub enum DisabledReasonCode {
Subsumed,
OverBroad,
RequiresConstraint,
PendingAdapterFact,
}
impl DisabledReasonCode {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Subsumed => "subsumed",
Self::OverBroad => "over-broad",
Self::RequiresConstraint => "requires-constraint",
Self::PendingAdapterFact => "pending-adapter-fact",
}
}
#[must_use]
pub fn waits_on_reenable_work(&self) -> bool {
matches!(self, Self::RequiresConstraint | Self::PendingAdapterFact)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DisabledReason {
pub code: DisabledReasonCode,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subsumed_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reenable_when: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Rule {
pub id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<String>,
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disabled_reason: Option<DisabledReason>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub severity: Option<Severity>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trust: Option<TrustClass>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub cwe: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub owasp: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub frameworks: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub packages: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub imports: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub modules: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub manifests: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub lockfiles: Vec<String>,
#[serde(skip, default)]
pub package_matching: crate::loader::PackageMatchSemantics,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub payload_types: Vec<PayloadType>,
#[serde(rename = "match")]
pub match_spec: MatchSpec,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub analysis_semantics: Option<AnalysisSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub taint_semantics: Option<TaintSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lifecycle_transition: Option<LifecycleTransitionSemantics>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub returns_type: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub callback_param_types: Vec<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub callback_arg_index: Option<u32>,
#[serde(default, skip_serializing_if = "RuleConstraint::is_empty")]
pub constraints: RuleConstraint,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub match_examples: Vec<RuleMatchExample>,
pub description: String,
#[serde(skip)]
pub kind: RuleKind,
#[serde(default)]
pub language: String,
#[serde(skip)]
pub source_path: String,
}