libsast 3.1.0__tar.gz → 3.1.2__tar.gz
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.
- {libsast-3.1.0 → libsast-3.1.2}/PKG-INFO +2 -1
- {libsast-3.1.0 → libsast-3.1.2}/libsast/__init__.py +1 -1
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/choice_matcher.py +33 -19
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/pattern_matcher.py +35 -13
- {libsast-3.1.0 → libsast-3.1.2}/libsast/scanner.py +1 -0
- {libsast-3.1.0 → libsast-3.1.2}/pyproject.toml +2 -1
- {libsast-3.1.0 → libsast-3.1.2}/LICENSE +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/README.md +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/__main__.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/common.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/__init__.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/helpers.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/matchers.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/__init__.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/helpers.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/semantic_sgrep.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/exceptions.py +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/cwe.yaml +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_masvs.yaml +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_mobile_top10_2016.yaml +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_mobile_top10_2024.yaml +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_web_top10_2017.yaml +0 -0
- {libsast-3.1.0 → libsast-3.1.2}/libsast/standards.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libsast
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: A generic SAST library built on top of semgrep and regex
|
|
5
5
|
Home-page: https://github.com/ajinabraham/libsast
|
|
6
6
|
License: LGPL-3.0-or-later
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
21
|
Classifier: Topic :: Security
|
|
22
22
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Dist: billiard (>=4.2.1,<5.0.0)
|
|
23
24
|
Requires-Dist: pyyaml (>=6.0)
|
|
24
25
|
Requires-Dist: requests
|
|
25
26
|
Requires-Dist: semgrep (==1.86.0) ; sys_platform != "win32"
|
|
@@ -12,7 +12,7 @@ year = str(datetime.now().year)
|
|
|
12
12
|
__title__ = 'libsast'
|
|
13
13
|
__authors__ = 'Ajin Abraham'
|
|
14
14
|
__copyright__ = f'Copyright {year} Ajin Abraham, opensecurity.in'
|
|
15
|
-
__version__ = '3.1.
|
|
15
|
+
__version__ = '3.1.2'
|
|
16
16
|
__version_info__ = tuple(int(i) for i in __version__.split('.'))
|
|
17
17
|
__all__ = [
|
|
18
18
|
'Scanner',
|
|
@@ -24,6 +24,7 @@ class ChoiceMatcher:
|
|
|
24
24
|
self.scan_rules = get_rules(options.get('choice_rules'))
|
|
25
25
|
self.show_progress = options.get('show_progress')
|
|
26
26
|
self.cpu = options.get('cpu_core')
|
|
27
|
+
self.queue = options.get('queue')
|
|
27
28
|
self.alternative_path = options.get('alternative_path')
|
|
28
29
|
exts = options.get('choice_extensions')
|
|
29
30
|
self.exts = [ext.lower() for ext in exts] if exts else []
|
|
@@ -31,14 +32,18 @@ class ChoiceMatcher:
|
|
|
31
32
|
|
|
32
33
|
def scan(self, paths: list) -> dict:
|
|
33
34
|
"""Scan file(s) or directory per rule."""
|
|
34
|
-
if not (self.scan_rules and paths):
|
|
35
|
-
return
|
|
36
|
-
self.validate_rules()
|
|
37
|
-
|
|
38
35
|
if self.show_progress:
|
|
39
36
|
pbar = common.ProgressBar('Choice Match', len(self.scan_rules))
|
|
40
37
|
self.scan_rules = pbar.progress_loop(self.scan_rules)
|
|
41
38
|
|
|
39
|
+
file_contents = self.read_file_contents(paths)
|
|
40
|
+
return self.regex_scan(file_contents)
|
|
41
|
+
|
|
42
|
+
def read_file_contents(self, paths: list) -> list:
|
|
43
|
+
"""Load file(s) content."""
|
|
44
|
+
if not (self.scan_rules and paths):
|
|
45
|
+
return
|
|
46
|
+
self.validate_rules()
|
|
42
47
|
choice_args = []
|
|
43
48
|
for rule in self.scan_rules:
|
|
44
49
|
scan_paths = paths
|
|
@@ -46,26 +51,35 @@ class ChoiceMatcher:
|
|
|
46
51
|
# Scan only alternative path
|
|
47
52
|
scan_paths = [Path(self.alternative_path)]
|
|
48
53
|
choice_args.append((scan_paths, rule))
|
|
54
|
+
if not choice_args:
|
|
55
|
+
return []
|
|
49
56
|
|
|
50
|
-
# Use ThreadPoolExecutor for
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
max_workers=self.cpu) as cpu_executor:
|
|
57
|
+
# Use ThreadPoolExecutor for file reading
|
|
58
|
+
with ThreadPoolExecutor() as io_executor:
|
|
59
|
+
# Submit file reading tasks and wait for results
|
|
54
60
|
futures = []
|
|
55
61
|
for args_tuple in choice_args:
|
|
56
|
-
# Submit each read task and store the future along with the args
|
|
57
62
|
future = io_executor.submit(
|
|
58
63
|
self._read_file_contents, args_tuple)
|
|
59
|
-
futures.append(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
futures.append(future)
|
|
65
|
+
return [future.result() for future in futures]
|
|
66
|
+
|
|
67
|
+
def regex_scan(self, file_contents) -> list:
|
|
68
|
+
"""Process regex matches on the file contents."""
|
|
69
|
+
if self.queue:
|
|
70
|
+
# Use billiard's pool for regex (support queues)
|
|
71
|
+
from billiard import Pool
|
|
72
|
+
with Pool(processes=self.cpu) as pool:
|
|
73
|
+
# Run regex on file data
|
|
74
|
+
results = pool.map(
|
|
75
|
+
self.choice_matcher,
|
|
76
|
+
file_contents)
|
|
77
|
+
else:
|
|
78
|
+
# Use ProcessPoolExecutor for regex processing
|
|
79
|
+
with ProcessPoolExecutor(max_workers=self.cpu) as cpu_executor:
|
|
80
|
+
results = list(cpu_executor.map(
|
|
81
|
+
self.choice_matcher,
|
|
82
|
+
file_contents))
|
|
69
83
|
|
|
70
84
|
self.add_finding(results)
|
|
71
85
|
return self.findings
|
|
@@ -25,39 +25,61 @@ class PatternMatcher:
|
|
|
25
25
|
self.scan_rules = get_rules(options.get('match_rules'))
|
|
26
26
|
self.show_progress = options.get('show_progress')
|
|
27
27
|
self.cpu = options.get('cpu_core')
|
|
28
|
+
self.queue = options.get('queue')
|
|
28
29
|
exts = options.get('match_extensions')
|
|
29
30
|
self.exts = [ext.lower() for ext in exts] if exts else []
|
|
30
31
|
self.findings = {}
|
|
31
32
|
|
|
32
33
|
def scan(self, paths: list) -> dict:
|
|
33
34
|
"""Scan file(s) or directory."""
|
|
34
|
-
if not (self.scan_rules and paths):
|
|
35
|
-
return
|
|
36
|
-
self.validate_rules()
|
|
37
|
-
|
|
38
35
|
if self.show_progress:
|
|
39
36
|
pbar = common.ProgressBar('Pattern Match', len(paths))
|
|
40
37
|
paths = pbar.progress_loop(paths)
|
|
41
38
|
|
|
39
|
+
file_contents = self.read_file_contents(paths)
|
|
40
|
+
return self.regex_scan(file_contents)
|
|
41
|
+
|
|
42
|
+
def read_file_contents(self, paths: list) -> list:
|
|
43
|
+
"""Load file(s) content."""
|
|
44
|
+
if not (self.scan_rules and paths):
|
|
45
|
+
return
|
|
46
|
+
self.validate_rules()
|
|
47
|
+
|
|
42
48
|
# Filter files by extension and size, prepare list for processing
|
|
43
49
|
files_to_scan = {
|
|
44
50
|
sfile for sfile in paths
|
|
45
51
|
if is_file_valid(sfile, self.exts, 5)
|
|
46
52
|
}
|
|
53
|
+
if not files_to_scan:
|
|
54
|
+
return []
|
|
47
55
|
|
|
48
|
-
# Use a ThreadPool for file reading
|
|
49
|
-
with ThreadPoolExecutor() as io_executor
|
|
50
|
-
max_workers=self.cpu) as cpu_executor:
|
|
56
|
+
# Use a ThreadPool for file reading
|
|
57
|
+
with ThreadPoolExecutor() as io_executor:
|
|
51
58
|
|
|
52
59
|
# Read all files
|
|
53
60
|
file_contents = list(io_executor.map(
|
|
54
61
|
self._read_file_content, files_to_scan))
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
62
|
+
return file_contents
|
|
63
|
+
|
|
64
|
+
def regex_scan(self, file_contents: list) -> dict:
|
|
65
|
+
"""Scan file(s) content."""
|
|
66
|
+
if self.queue:
|
|
67
|
+
# Use billiard's pool for CPU-bound regex (support queues)
|
|
68
|
+
from billiard import Pool
|
|
69
|
+
with Pool(processes=self.cpu) as cpu_executor:
|
|
70
|
+
# Run regex on file data
|
|
71
|
+
results = cpu_executor.map(
|
|
72
|
+
self.pattern_matcher,
|
|
73
|
+
file_contents,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
# Use a ProcessPool for CPU-bound regex
|
|
77
|
+
with ProcessPoolExecutor(max_workers=self.cpu) as cpu_executor:
|
|
78
|
+
# Run regex on file data
|
|
79
|
+
results = cpu_executor.map(
|
|
80
|
+
self.pattern_matcher,
|
|
81
|
+
file_contents,
|
|
82
|
+
)
|
|
61
83
|
|
|
62
84
|
# Compile findings
|
|
63
85
|
self.add_finding(results)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "libsast"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.2"
|
|
4
4
|
description = "A generic SAST library built on top of semgrep and regex"
|
|
5
5
|
keywords = ["libsast", "SAST", "Python SAST", "SAST API", "Regex SAST", "Pattern Matcher"]
|
|
6
6
|
authors = ["Ajin Abraham <ajin@opensecurity.in>"]
|
|
@@ -27,6 +27,7 @@ python = "^3.8"
|
|
|
27
27
|
requests = "*"
|
|
28
28
|
pyyaml = ">=6.0"
|
|
29
29
|
semgrep = {version = "1.86.0", markers = "sys_platform != 'win32'"}
|
|
30
|
+
billiard = "^4.2.1"
|
|
30
31
|
|
|
31
32
|
[tool.poetry.group.dev.dependencies]
|
|
32
33
|
bandit = "*"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|