trapline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +585 -0
- data/config.ru.example +21 -0
- data/exe/trapline +8 -0
- data/lib/generators/trapline/install_generator.rb +34 -0
- data/lib/generators/trapline/templates/initializer.rb +23 -0
- data/lib/tasks/trapline.rake +19 -0
- data/lib/trapline/cli.rb +212 -0
- data/lib/trapline/configuration.rb +19 -0
- data/lib/trapline/cve/client.rb +203 -0
- data/lib/trapline/cve/record.rb +160 -0
- data/lib/trapline/engine.rb +243 -0
- data/lib/trapline/generator.rb +236 -0
- data/lib/trapline/languages.rb +65 -0
- data/lib/trapline/railtie.rb +26 -0
- data/lib/trapline/rule.rb +165 -0
- data/lib/trapline/runtime.rb +148 -0
- data/lib/trapline/store.rb +113 -0
- data/lib/trapline/suite.rb +44 -0
- data/lib/trapline/templates/cwe/cwe-22.yml +167 -0
- data/lib/trapline/templates/cwe/cwe-327.yml +124 -0
- data/lib/trapline/templates/cwe/cwe-502.yml +123 -0
- data/lib/trapline/templates/cwe/cwe-601.yml +124 -0
- data/lib/trapline/templates/cwe/cwe-611.yml +87 -0
- data/lib/trapline/templates/cwe/cwe-78.yml +166 -0
- data/lib/trapline/templates/cwe/cwe-79.yml +164 -0
- data/lib/trapline/templates/cwe/cwe-798.yml +125 -0
- data/lib/trapline/templates/cwe/cwe-89.yml +189 -0
- data/lib/trapline/templates/cwe/cwe-917.yml +46 -0
- data/lib/trapline/templates/cwe/cwe-918.yml +166 -0
- data/lib/trapline/templates/cwe/cwe-94.yml +131 -0
- data/lib/trapline/templates.rb +261 -0
- data/lib/trapline/validator.rb +359 -0
- data/lib/trapline/version.rb +5 -0
- data/lib/trapline/web/app.rb +234 -0
- data/lib/trapline/web/assets/THIRD_PARTY_LICENSES.txt +214 -0
- data/lib/trapline/web/assets/app.css +399 -0
- data/lib/trapline/web/assets/app.js +804 -0
- data/lib/trapline/web/assets/codemirror.css +344 -0
- data/lib/trapline/web/assets/codemirror.js +13263 -0
- data/lib/trapline/web/assets/index.html +165 -0
- data/lib/trapline/web/assets/jetbrains-mono-latin-400-normal.woff2 +0 -0
- data/lib/trapline/web/assets/jetbrains-mono-latin-600-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-400-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-500-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-600-normal.woff2 +0 -0
- data/lib/trapline/web/assets/public-sans-latin-700-normal.woff2 +0 -0
- data/lib/trapline/web/server.rb +108 -0
- data/lib/trapline.rb +70 -0
- metadata +136 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
cwe: CWE-94
|
|
2
|
+
name: "Improper Control of Generation of Code ('Code Injection')"
|
|
3
|
+
title: Code injection
|
|
4
|
+
owasp: ["A03:2021 - Injection"]
|
|
5
|
+
# Only the vulnerability class: "remote code execution"/"arbitrary code" describe an
|
|
6
|
+
# impact shared by memory corruption, deserialization, command injection...
|
|
7
|
+
keywords: ["code injection", "eval", "template injection", "ssti"]
|
|
8
|
+
message: >-
|
|
9
|
+
Untrusted input is evaluated as code. Never pass user-controlled data to
|
|
10
|
+
eval-like functions; map inputs to an allowlist of operations instead.
|
|
11
|
+
languages:
|
|
12
|
+
ruby:
|
|
13
|
+
rule: |
|
|
14
|
+
mode: taint
|
|
15
|
+
pattern-sources:
|
|
16
|
+
- pattern: params
|
|
17
|
+
- pattern: cookies
|
|
18
|
+
pattern-sinks:
|
|
19
|
+
- pattern: eval(...)
|
|
20
|
+
- pattern: $O.instance_eval(...)
|
|
21
|
+
- pattern: $O.class_eval(...)
|
|
22
|
+
- pattern: $O.module_eval(...)
|
|
23
|
+
- pattern: $X.constantize
|
|
24
|
+
- pattern: $X.safe_constantize
|
|
25
|
+
- pattern: ERB.new(...)
|
|
26
|
+
- patterns:
|
|
27
|
+
- pattern-either:
|
|
28
|
+
- pattern: $O.send($M, ...)
|
|
29
|
+
- pattern: $O.public_send($M, ...)
|
|
30
|
+
- focus-metavariable: $M
|
|
31
|
+
test: |
|
|
32
|
+
class CalcController < ApplicationController
|
|
33
|
+
def run
|
|
34
|
+
# ruleid: __RULE_ID__
|
|
35
|
+
result = eval(params[:expr])
|
|
36
|
+
# ruleid: __RULE_ID__
|
|
37
|
+
klass = params[:type].constantize
|
|
38
|
+
# ruleid: __RULE_ID__
|
|
39
|
+
report.public_send(params[:format])
|
|
40
|
+
# ok: __RULE_ID__
|
|
41
|
+
klass = { "user" => User, "admin" => Admin }.fetch(params[:type])
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
python:
|
|
45
|
+
rule: |
|
|
46
|
+
mode: taint
|
|
47
|
+
pattern-sources:
|
|
48
|
+
- pattern: flask.request.$ANY
|
|
49
|
+
- patterns:
|
|
50
|
+
- pattern-inside: |
|
|
51
|
+
def $VIEW(request, ...):
|
|
52
|
+
...
|
|
53
|
+
- pattern: request.$ANY
|
|
54
|
+
pattern-sinks:
|
|
55
|
+
- pattern: eval(...)
|
|
56
|
+
- pattern: exec(...)
|
|
57
|
+
- pattern: compile(...)
|
|
58
|
+
- pattern: flask.render_template_string(...)
|
|
59
|
+
- pattern: jinja2.Template(...)
|
|
60
|
+
- pattern: importlib.import_module(...)
|
|
61
|
+
test: |
|
|
62
|
+
import ast
|
|
63
|
+
from flask import request, render_template_string
|
|
64
|
+
|
|
65
|
+
def calc():
|
|
66
|
+
expr = request.args.get("expr")
|
|
67
|
+
# ruleid: __RULE_ID__
|
|
68
|
+
result = eval(expr)
|
|
69
|
+
# ruleid: __RULE_ID__
|
|
70
|
+
page = render_template_string("<p>" + expr + "</p>")
|
|
71
|
+
# ok: __RULE_ID__
|
|
72
|
+
value = ast.literal_eval(expr)
|
|
73
|
+
javascript:
|
|
74
|
+
rule: |
|
|
75
|
+
mode: taint
|
|
76
|
+
pattern-sources:
|
|
77
|
+
- pattern: $REQ.query
|
|
78
|
+
- pattern: $REQ.body
|
|
79
|
+
- pattern: $REQ.params
|
|
80
|
+
pattern-sinks:
|
|
81
|
+
- pattern: eval(...)
|
|
82
|
+
- pattern: Function(...)
|
|
83
|
+
- pattern: new Function(...)
|
|
84
|
+
- pattern: $VM.runInNewContext(...)
|
|
85
|
+
- pattern: $VM.runInThisContext(...)
|
|
86
|
+
- patterns:
|
|
87
|
+
- pattern-either:
|
|
88
|
+
- pattern: setTimeout($CODE, ...)
|
|
89
|
+
- pattern: setInterval($CODE, ...)
|
|
90
|
+
- focus-metavariable: $CODE
|
|
91
|
+
test: |
|
|
92
|
+
app.get("/calc", (req, res) => {
|
|
93
|
+
// ruleid: __RULE_ID__
|
|
94
|
+
const result = eval(req.query.expr);
|
|
95
|
+
// ruleid: __RULE_ID__
|
|
96
|
+
const fn = new Function("x", req.body.code);
|
|
97
|
+
// ok: __RULE_ID__
|
|
98
|
+
const n = JSON.parse(req.query.expr);
|
|
99
|
+
res.json({ result, n });
|
|
100
|
+
});
|
|
101
|
+
java:
|
|
102
|
+
rule: |
|
|
103
|
+
mode: taint
|
|
104
|
+
pattern-sources:
|
|
105
|
+
- pattern: (javax.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
106
|
+
- pattern: (jakarta.servlet.http.HttpServletRequest $REQ).$GET(...)
|
|
107
|
+
- patterns:
|
|
108
|
+
- pattern-inside: |
|
|
109
|
+
$RET $METHOD(..., @RequestParam $T $P, ...) { ... }
|
|
110
|
+
- pattern: $P
|
|
111
|
+
pattern-sinks:
|
|
112
|
+
- pattern: (javax.script.ScriptEngine $E).eval(...)
|
|
113
|
+
- pattern: (ScriptEngine $E).eval(...)
|
|
114
|
+
- pattern: (ExpressionParser $P).parseExpression(...)
|
|
115
|
+
- pattern: (SpelExpressionParser $P).parseExpression(...)
|
|
116
|
+
- pattern: Ognl.getValue(...)
|
|
117
|
+
test: |
|
|
118
|
+
import javax.servlet.http.HttpServletRequest;
|
|
119
|
+
import javax.script.ScriptEngine;
|
|
120
|
+
import javax.script.ScriptEngineManager;
|
|
121
|
+
|
|
122
|
+
public class CalcServlet {
|
|
123
|
+
public void doGet(HttpServletRequest request) throws Exception {
|
|
124
|
+
String expr = request.getParameter("expr");
|
|
125
|
+
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
|
|
126
|
+
// ruleid: __RULE_ID__
|
|
127
|
+
Object result = engine.eval(expr);
|
|
128
|
+
// ok: __RULE_ID__
|
|
129
|
+
Object fixed = engine.eval("1 + 1");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Trapline
|
|
6
|
+
module Templates
|
|
7
|
+
DIR = File.expand_path("templates/cwe", __dir__)
|
|
8
|
+
|
|
9
|
+
CWE_ALIASES = {
|
|
10
|
+
"CWE-77" => "CWE-78", "CWE-88" => "CWE-78",
|
|
11
|
+
"CWE-80" => "CWE-79", "CWE-83" => "CWE-79", "CWE-87" => "CWE-79",
|
|
12
|
+
"CWE-23" => "CWE-22", "CWE-35" => "CWE-22", "CWE-36" => "CWE-22", "CWE-73" => "CWE-22",
|
|
13
|
+
"CWE-564" => "CWE-89", "CWE-943" => "CWE-89",
|
|
14
|
+
"CWE-95" => "CWE-94", "CWE-1336" => "CWE-94", "CWE-96" => "CWE-94",
|
|
15
|
+
"CWE-326" => "CWE-327", "CWE-328" => "CWE-327", "CWE-916" => "CWE-327",
|
|
16
|
+
"CWE-259" => "CWE-798", "CWE-321" => "CWE-798",
|
|
17
|
+
"CWE-776" => "CWE-611", "CWE-827" => "CWE-611",
|
|
18
|
+
"CWE-1188" => "CWE-917"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
Template = Struct.new(:cwe, :name, :title, :owasp, :keywords, :message, :languages, keyword_init: true) do
|
|
22
|
+
def label
|
|
23
|
+
"#{cwe}: #{name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def supports?(lang)
|
|
27
|
+
languages.key?(lang.to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def body(lang)
|
|
31
|
+
YAML.safe_load(languages.fetch(lang.to_s)["rule"])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_code(lang, rule_id)
|
|
35
|
+
languages.fetch(lang.to_s)["test"].gsub("__RULE_ID__", rule_id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def summary
|
|
39
|
+
{ cwe: cwe, name: name, title: title, languages: languages.keys }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
STARTERS = {
|
|
44
|
+
"search" => {
|
|
45
|
+
title: "Simple search (pattern)",
|
|
46
|
+
body: { "pattern" => "dangerous_call(...)" },
|
|
47
|
+
test: {
|
|
48
|
+
"ruby" => "# ruleid: __RULE_ID__\ndangerous_call(1)\n# ok: __RULE_ID__\nsafe_call(1)\n",
|
|
49
|
+
"python" => "# ruleid: __RULE_ID__\ndangerous_call(1)\n# ok: __RULE_ID__\nsafe_call(1)\n",
|
|
50
|
+
"javascript" => "// ruleid: __RULE_ID__\ndangerous_call(1);\n// ok: __RULE_ID__\nsafe_call(1);\n"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"patterns" => {
|
|
54
|
+
title: "Combination (patterns + pattern-not)",
|
|
55
|
+
body: {
|
|
56
|
+
"patterns" => [
|
|
57
|
+
{ "pattern" => "$OBJ.$METHOD($ARG)" },
|
|
58
|
+
{ "pattern-not" => "$OBJ.$METHOD(\"...\")" },
|
|
59
|
+
{ "metavariable-regex" => { "metavariable" => "$METHOD", "regex" => "^(eval|exec)$" } }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
test: {
|
|
63
|
+
"ruby" => "# ruleid: __RULE_ID__\nobj.eval(input)\n# ok: __RULE_ID__\nobj.eval(\"1 + 1\")\n",
|
|
64
|
+
"python" => "# ruleid: __RULE_ID__\nobj.eval(data)\n# ok: __RULE_ID__\nobj.eval(\"1 + 1\")\n",
|
|
65
|
+
"javascript" => "// ruleid: __RULE_ID__\nobj.eval(input);\n// ok: __RULE_ID__\nobj.eval(\"1 + 1\");\n"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"taint" => {
|
|
69
|
+
title: "Data flow (taint)",
|
|
70
|
+
body: {
|
|
71
|
+
"mode" => "taint",
|
|
72
|
+
"pattern-sources" => [{ "pattern" => "user_input(...)" }],
|
|
73
|
+
"pattern-sanitizers" => [{ "pattern" => "sanitize(...)" }],
|
|
74
|
+
"pattern-sinks" => [{ "pattern" => "dangerous_sink(...)" }]
|
|
75
|
+
},
|
|
76
|
+
test: {
|
|
77
|
+
"ruby" => "# ruleid: __RULE_ID__\ndangerous_sink(user_input(:q))\n# ok: __RULE_ID__\ndangerous_sink(sanitize(user_input(:q)))\n",
|
|
78
|
+
"python" => "# ruleid: __RULE_ID__\ndangerous_sink(user_input(\"q\"))\n# ok: __RULE_ID__\ndangerous_sink(sanitize(user_input(\"q\")))\n",
|
|
79
|
+
"javascript" => "// ruleid: __RULE_ID__\ndangerous_sink(user_input(\"q\"));\n// ok: __RULE_ID__\ndangerous_sink(sanitize(user_input(\"q\")));\n",
|
|
80
|
+
"java" => "class Example {\n void run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"));\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")));\n }\n}\n",
|
|
81
|
+
"go" => "package main\n\nfunc run() {\n\t// ruleid: __RULE_ID__\n\tdangerous_sink(user_input(\"q\"))\n\t// ok: __RULE_ID__\n\tdangerous_sink(sanitize(user_input(\"q\")))\n}\n",
|
|
82
|
+
"typescript" => "// ruleid: __RULE_ID__\ndangerous_sink(user_input(\"q\"));\n// ok: __RULE_ID__\ndangerous_sink(sanitize(user_input(\"q\")));\n",
|
|
83
|
+
"php" => "<?php\n// ruleid: __RULE_ID__\ndangerous_sink(user_input(\"q\"));\n// ok: __RULE_ID__\ndangerous_sink(sanitize(user_input(\"q\")));\n",
|
|
84
|
+
"csharp" => "class Example {\n void Run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"));\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")));\n }\n}\n",
|
|
85
|
+
"kotlin" => "fun run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"))\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")))\n}\n",
|
|
86
|
+
"scala" => "object Example {\n def run(): Unit = {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"))\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")))\n }\n}\n",
|
|
87
|
+
"rust" => "fn run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"));\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")));\n}\n",
|
|
88
|
+
"c" => "void run(void) {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"));\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")));\n}\n",
|
|
89
|
+
"cpp" => "void run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"));\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")));\n}\n",
|
|
90
|
+
"swift" => "func run() {\n // ruleid: __RULE_ID__\n dangerous_sink(user_input(\"q\"))\n // ok: __RULE_ID__\n dangerous_sink(sanitize(user_input(\"q\")))\n}\n"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"regex" => {
|
|
94
|
+
title: "Regular expression (pattern-regex)",
|
|
95
|
+
body: { "pattern-regex" => "(?i)todo:\\s*security" },
|
|
96
|
+
test: { "generic" => "# ruleid: __RULE_ID__\n# TODO: security review this\n" }
|
|
97
|
+
}
|
|
98
|
+
}.freeze
|
|
99
|
+
|
|
100
|
+
module_function
|
|
101
|
+
|
|
102
|
+
def all
|
|
103
|
+
@all ||= Dir.glob(File.join(DIR, "*.yml")).sort_by { |f| f[/\d+/].to_i }.map do |file|
|
|
104
|
+
data = YAML.safe_load(File.read(file))
|
|
105
|
+
Template.new(**data.transform_keys(&:to_sym).slice(*Template.members))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def for_cwe(cwe)
|
|
110
|
+
id = Cve::Record.normalize_cwe(cwe)
|
|
111
|
+
id = CWE_ALIASES.fetch(id, id)
|
|
112
|
+
all.find { |t| t.cwe == id }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def cwe_name(cwe)
|
|
116
|
+
all.find { |t| t.cwe == cwe }&.label || cwe
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def detect(text)
|
|
120
|
+
text = text.to_s.downcase
|
|
121
|
+
return [] if text.empty?
|
|
122
|
+
|
|
123
|
+
all.map { |t| [t, Array(t.keywords).count { |k| keyword_regex(k).match?(text) }] }
|
|
124
|
+
.select { |_, score| score.positive? }
|
|
125
|
+
.sort_by { |_, score| -score }
|
|
126
|
+
.map(&:first)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Whole words ("eval" must not hit "retrieval"), except at edges that are
|
|
130
|
+
# not word characters ("../").
|
|
131
|
+
def keyword_regex(keyword)
|
|
132
|
+
k = keyword.downcase
|
|
133
|
+
Regexp.new("#{'\\b' if k.match?(/\A\w/)}#{Regexp.escape(k)}#{'\\b' if k.match?(/\w\z/)}")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def starter(kind, language: "ruby", id: "my-rule")
|
|
137
|
+
spec = STARTERS.fetch(kind.to_s) { STARTERS["search"] }
|
|
138
|
+
lang = language.to_s
|
|
139
|
+
test = spec[:test][lang] || spec[:test]["generic"] || placeholder_test(lang)
|
|
140
|
+
langs = kind.to_s == "regex" ? ["regex"] : [lang]
|
|
141
|
+
rule = Rule.new({ "id" => id, "message" => "Describe the problem and how to fix it.", "severity" => "WARNING",
|
|
142
|
+
"languages" => langs, **spec[:body],
|
|
143
|
+
"metadata" => { "category" => "security", "confidence" => "MEDIUM" } })
|
|
144
|
+
{ yaml: rule.to_yaml, test_code: test.gsub("__RULE_ID__", id), language: langs.first }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def placeholder_test(lang)
|
|
148
|
+
c = Languages.comment_for(lang)
|
|
149
|
+
"#{c} Write an example of vulnerable code and a fixed one here.\n" \
|
|
150
|
+
"#{c} Before the line that MUST produce a finding, add a comment with ruleid, a colon and the rule id;\n" \
|
|
151
|
+
"#{c} before the one that must NOT, use ok instead of ruleid.\n"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def dependency(package, rule_id)
|
|
155
|
+
lang = package.language
|
|
156
|
+
name = package.name.to_s
|
|
157
|
+
body, test = case lang
|
|
158
|
+
when "ruby" then ruby_dependency(name, rule_id)
|
|
159
|
+
when "python" then python_dependency(name, rule_id)
|
|
160
|
+
when "javascript" then js_dependency(name, rule_id)
|
|
161
|
+
when "java" then java_dependency(name, rule_id)
|
|
162
|
+
when "go" then go_dependency(name, rule_id)
|
|
163
|
+
end
|
|
164
|
+
return nil unless body
|
|
165
|
+
|
|
166
|
+
{ body: body, test: test, language: lang }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def ruby_dependency(name, id)
|
|
170
|
+
paths = [name, name.tr("-", "/")].uniq
|
|
171
|
+
body = { "pattern-either" => paths.map { |p| { "pattern" => "require \"#{p}\"" } } +
|
|
172
|
+
[{ "pattern" => "gem \"#{name}\", ..." }] }
|
|
173
|
+
test = <<~RUBY
|
|
174
|
+
# ruleid: #{id}
|
|
175
|
+
gem "#{name}", "~> 1.0"
|
|
176
|
+
# ruleid: #{id}
|
|
177
|
+
require "#{paths.last}"
|
|
178
|
+
# ok: #{id}
|
|
179
|
+
require "json"
|
|
180
|
+
RUBY
|
|
181
|
+
[body, test]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
PYTHON_IMPORTS = { "pyyaml" => "yaml", "pillow" => "PIL", "beautifulsoup4" => "bs4",
|
|
185
|
+
"scikit-learn" => "sklearn", "python-dateutil" => "dateutil",
|
|
186
|
+
"pyjwt" => "jwt", "pycryptodome" => "Crypto", "opencv-python" => "cv2" }.freeze
|
|
187
|
+
|
|
188
|
+
def python_dependency(name, id)
|
|
189
|
+
mod = PYTHON_IMPORTS.fetch(name.downcase) { name.downcase.tr("-", "_").tr(".", "_") }
|
|
190
|
+
body = { "pattern-either" => [
|
|
191
|
+
{ "pattern" => "import #{mod}" },
|
|
192
|
+
{ "pattern" => "import #{mod}.$SUB" },
|
|
193
|
+
{ "pattern" => "from #{mod} import $X" },
|
|
194
|
+
{ "pattern" => "from #{mod}.$SUB import $X" }
|
|
195
|
+
] }
|
|
196
|
+
test = <<~PY
|
|
197
|
+
# ruleid: #{id}
|
|
198
|
+
import #{mod}
|
|
199
|
+
# ruleid: #{id}
|
|
200
|
+
from #{mod} import something
|
|
201
|
+
# ok: #{id}
|
|
202
|
+
import json
|
|
203
|
+
PY
|
|
204
|
+
[body, test]
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def js_dependency(name, id)
|
|
208
|
+
re = "=~/^#{Regexp.escape(name).gsub('/', '\/')}(\\/.*)?$/"
|
|
209
|
+
body = { "pattern-either" => [
|
|
210
|
+
{ "pattern" => "require(\"#{re}\")" },
|
|
211
|
+
{ "pattern" => "import $X from \"#{re}\"" },
|
|
212
|
+
{ "pattern" => "import * as $X from \"#{re}\"" },
|
|
213
|
+
{ "pattern" => "import {...} from \"#{re}\"" },
|
|
214
|
+
{ "pattern" => "import \"#{re}\"" }
|
|
215
|
+
] }
|
|
216
|
+
test = <<~JS
|
|
217
|
+
// ruleid: #{id}
|
|
218
|
+
const lib = require("#{name}");
|
|
219
|
+
// ruleid: #{id}
|
|
220
|
+
import thing from "#{name}";
|
|
221
|
+
// ok: #{id}
|
|
222
|
+
const fs = require("fs");
|
|
223
|
+
JS
|
|
224
|
+
[body, test]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def java_dependency(name, id)
|
|
228
|
+
group = name.split(":").first.to_s
|
|
229
|
+
return nil if group.empty?
|
|
230
|
+
|
|
231
|
+
body = { "pattern-regex" => "(?m)^\\s*import\\s+(static\\s+)?#{Regexp.escape(group)}\\." }
|
|
232
|
+
test = <<~JAVA
|
|
233
|
+
// ruleid: #{id}
|
|
234
|
+
import #{group}.Example;
|
|
235
|
+
// ok: #{id}
|
|
236
|
+
import java.util.List;
|
|
237
|
+
|
|
238
|
+
public class App {}
|
|
239
|
+
JAVA
|
|
240
|
+
[body, test]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def go_dependency(name, id)
|
|
244
|
+
# A regex: `import "..."` patterns report the whole import block on its first line.
|
|
245
|
+
body = { "pattern-regex" => "(?m)^\\s*(import\\s+)?([\\w.]+\\s+)?\"#{Regexp.escape(name)}(/[^\"]*)?\"\\s*$" }
|
|
246
|
+
test = <<~GO
|
|
247
|
+
package main
|
|
248
|
+
|
|
249
|
+
import (
|
|
250
|
+
// ruleid: #{id}
|
|
251
|
+
"#{name}"
|
|
252
|
+
// ok: #{id}
|
|
253
|
+
"fmt"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
func main() { fmt.Println(x) }
|
|
257
|
+
GO
|
|
258
|
+
[body, test]
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|