use crate::labels::{
Cap, DataLabel, GateActivation, Kind, LabelRule, ParamConfig, RuntimeLabelRule, SinkGate,
};
use crate::utils::project::{DetectedFramework, FrameworkContext};
use phf::{Map, phf_map};
pub static RULES: &[LabelRule] = &[
LabelRule {
matchers: &[
"document.location",
"window.location",
"req.body",
"req.query",
"req.params",
"req.headers",
"req.cookies",
"req.hostname",
"req.ip",
"req.path",
"req.protocol",
"req.url",
"req.get",
"req.header",
"process.env",
"location.search",
"location.hash",
],
label: DataLabel::Source(Cap::all()),
case_sensitive: false,
},
LabelRule {
matchers: &["JSON.parse"],
label: DataLabel::Sanitizer(Cap::JSON_PARSE),
case_sensitive: false,
},
LabelRule {
matchers: &["encodeURIComponent"],
label: DataLabel::Sanitizer(Cap::from_bits_truncate(
Cap::URL_ENCODE.bits() | Cap::HTML_ESCAPE.bits(),
)),
case_sensitive: false,
},
LabelRule {
matchers: &["encodeURI"],
label: DataLabel::Sanitizer(Cap::URL_ENCODE),
case_sensitive: false,
},
LabelRule {
matchers: &["DOMPurify.sanitize"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["xss"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["sanitizeHtml"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["validator.escape"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["parseInt", "parseFloat", "Number"],
label: DataLabel::Sanitizer(Cap::all()),
case_sensitive: true,
},
LabelRule {
matchers: &["sanitizeUrl"],
label: DataLabel::Sanitizer(Cap::URL_ENCODE),
case_sensitive: false,
},
LabelRule {
matchers: &["shell-escape", "shellescape"],
label: DataLabel::Sanitizer(Cap::SHELL_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["he.encode", "he.escape"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &[
"serializeForUpstream",
"forwardPayload",
"tracker.send",
"analytics.track",
"metrics.report",
"logEvent",
],
label: DataLabel::Sanitizer(Cap::DATA_EXFIL),
case_sensitive: false,
},
LabelRule {
matchers: &["escapeHtml", "escapeHTML", "htmlEscape", "safeHtml"],
label: DataLabel::Sanitizer(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["eval"],
label: DataLabel::Sink(Cap::CODE_EXEC),
case_sensitive: false,
},
LabelRule {
matchers: &["innerHTML", "dangerouslySetInnerHTML"],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &[
"child_process.exec",
"child_process.execSync",
"child_process.spawn",
"child_process.execFile",
"exec",
"execSync",
"execFile",
"execAsync",
"execPromise",
],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
},
LabelRule {
matchers: &[
"location.href",
"window.location.href",
"document.location.href",
],
label: DataLabel::Sink(Cap::URL_ENCODE),
case_sensitive: false,
},
LabelRule {
matchers: &["res.send", "res.json"],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["res.redirect"],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
},
LabelRule {
matchers: &["res.sendFile", "res.download"],
label: DataLabel::Sink(Cap::FILE_IO),
case_sensitive: false,
},
LabelRule {
matchers: &["res.set", "res.header"],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &[
"document.write",
"document.writeln",
"outerHTML",
"insertAdjacentHTML",
],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
},
LabelRule {
matchers: &["location.assign", "location.replace", "window.open"],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
},
LabelRule {
matchers: &[
"fs.writeFile",
"fs.writeFileSync",
"fs.readFile",
"fs.readFileSync",
"fs.createReadStream",
"fs.createWriteStream",
"fs.access",
"fs.stat",
"fs.statSync",
"fs.unlink",
"fs.unlinkSync",
"fs.readdir",
"fs.readdirSync",
],
label: DataLabel::Sink(Cap::FILE_IO),
case_sensitive: false,
},
LabelRule {
matchers: &["net.createConnection"],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
},
LabelRule {
matchers: &["HttpClient.send", "XMLHttpRequest.prototype.send"],
label: DataLabel::Sink(Cap::DATA_EXFIL),
case_sensitive: false,
},
LabelRule {
matchers: &[
"connection.query",
"client.query",
"pool.query",
"db.query",
"db.execute",
],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: false,
},
LabelRule {
matchers: &["sequelize.query", "knex.raw", "$queryRaw", "$executeRaw"],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: true,
},
];
pub static EXCLUDES: &[&str] = &[
"router.get",
"router.post",
"router.put",
"router.delete",
"router.patch",
"router.use",
"router.all",
"app.get",
"app.post",
"app.put",
"app.delete",
"app.patch",
"app.use",
"app.all",
"req.session",
"req.app",
"req.route",
"req.next",
"container.exec",
"exec.start",
];
pub static GATED_SINKS: &[SinkGate] = &[
SinkGate {
callee_matcher: "setAttribute",
arg_index: 0,
dangerous_values: &["href", "src", "action", "formaction", "srcdoc"],
dangerous_prefixes: &["on"],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
payload_args: &[1],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::ValueMatch,
},
SinkGate {
callee_matcher: "parseFromString",
arg_index: 1,
dangerous_values: &["text/html", "application/xhtml+xml"],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::ValueMatch,
},
SinkGate {
callee_matcher: "fetch",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["url"],
},
},
SinkGate {
callee_matcher: "axios",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["url", "baseURL"],
},
},
SinkGate {
callee_matcher: "axios.request",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["url", "baseURL"],
},
},
SinkGate {
callee_matcher: "axios.get",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "axios.post",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "axios.put",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "axios.patch",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "axios.delete",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "got",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["url", "prefixUrl"],
},
},
SinkGate {
callee_matcher: "undici.request",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["origin", "path"],
},
},
SinkGate {
callee_matcher: "http.request",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["host", "hostname", "path", "protocol", "port", "origin"],
},
},
SinkGate {
callee_matcher: "https.request",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["host", "hostname", "path", "protocol", "port", "origin"],
},
},
SinkGate {
callee_matcher: "fetch",
arg_index: 1,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::DATA_EXFIL),
case_sensitive: false,
payload_args: &[1],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &["body", "headers", "json"],
},
},
SinkGate {
callee_matcher: "=exec",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "=execSync",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "=execFile",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "=execAsync",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "=execPromise",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SHELL_ESCAPE),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "$queryRawUnsafe",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
SinkGate {
callee_matcher: "$executeRawUnsafe",
arg_index: 0,
dangerous_values: &[],
dangerous_prefixes: &[],
label: DataLabel::Sink(Cap::SQL_QUERY),
case_sensitive: true,
payload_args: &[0],
keyword_name: None,
dangerous_kwargs: &[],
activation: GateActivation::Destination {
object_destination_fields: &[],
},
},
];
pub static KINDS: Map<&'static str, Kind> = phf_map! {
"if_statement" => Kind::If,
"while_statement" => Kind::While,
"for_statement" => Kind::For,
"for_in_statement" => Kind::For,
"do_statement" => Kind::While,
"return_statement" => Kind::Return,
"throw_statement" => Kind::Throw,
"break_statement" => Kind::Break,
"continue_statement" => Kind::Continue,
"program" => Kind::SourceFile,
"statement_block" => Kind::Block,
"else_clause" => Kind::Block,
"function_declaration" => Kind::Function,
"function_expression" => Kind::Function,
"arrow_function" => Kind::Function,
"method_definition" => Kind::Function,
"generator_function_declaration" => Kind::Function,
"generator_function" => Kind::Function,
"switch_statement" => Kind::Switch,
"switch_body" => Kind::Block,
"switch_case" => Kind::Block,
"switch_default" => Kind::Block,
"try_statement" => Kind::Try,
"catch_clause" => Kind::Block,
"finally_clause" => Kind::Block,
"class_declaration" => Kind::Block,
"class" => Kind::Block,
"class_body" => Kind::Block,
"abstract_class_declaration" => Kind::Block,
"export_statement" => Kind::Block,
"enum_declaration" => Kind::Trivia,
"call_expression" => Kind::CallFn,
"new_expression" => Kind::CallFn,
"assignment_expression" => Kind::Assignment,
"variable_declaration" => Kind::CallWrapper,
"lexical_declaration" => Kind::CallWrapper,
"expression_statement" => Kind::CallWrapper,
"as_expression" => Kind::Seq,
"type_assertion" => Kind::Seq,
"comment" => Kind::Trivia,
";" => Kind::Trivia, "," => Kind::Trivia,
"(" => Kind::Trivia, ")" => Kind::Trivia,
"{" => Kind::Trivia, "}" => Kind::Trivia,
"\n" => Kind::Trivia,
"import_statement" => Kind::Trivia,
"type_alias_declaration" => Kind::Trivia,
"interface_declaration" => Kind::Trivia,
};
pub static PARAM_CONFIG: ParamConfig = ParamConfig {
params_field: "parameters",
param_node_kinds: &["required_parameter", "optional_parameter", "identifier"],
self_param_kinds: &[],
ident_fields: &["name", "pattern"],
};
pub fn framework_rules(ctx: &FrameworkContext) -> Vec<RuntimeLabelRule> {
let mut rules = Vec::new();
if ctx.has(DetectedFramework::Koa) {
rules.push(RuntimeLabelRule {
matchers: vec![
"ctx.request.body".into(),
"ctx.request.query".into(),
"ctx.request.querystring".into(),
"ctx.request.params".into(),
"ctx.request.headers".into(),
"ctx.request.header".into(),
"ctx.request.get".into(),
"ctx.query".into(),
"ctx.params".into(),
"ctx.headers".into(),
"ctx.header".into(),
"ctx.get".into(),
"ctx.cookies.get".into(),
"ctx.hostname".into(),
"ctx.ip".into(),
"ctx.path".into(),
"ctx.protocol".into(),
"ctx.url".into(),
],
label: DataLabel::Source(Cap::all()),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["ctx.body".into()],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["ctx.redirect".into()],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["ctx.set".into(), "ctx.append".into()],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
});
}
if ctx.has(DetectedFramework::Fastify) {
rules.push(RuntimeLabelRule {
matchers: vec![
"request.body".into(),
"request.query".into(),
"request.params".into(),
"request.headers".into(),
"request.cookies".into(),
"request.hostname".into(),
"request.ip".into(),
"request.url".into(),
"request.raw.headers".into(),
],
label: DataLabel::Source(Cap::all()),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["reply.send".into()],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["reply.redirect".into()],
label: DataLabel::Sink(Cap::SSRF),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["reply.sendFile".into(), "reply.download".into()],
label: DataLabel::Sink(Cap::FILE_IO),
case_sensitive: false,
});
rules.push(RuntimeLabelRule {
matchers: vec!["reply.header".into(), "reply.headers".into()],
label: DataLabel::Sink(Cap::HTML_ESCAPE),
case_sensitive: false,
});
}
rules
}