libsast 3.0.2__tar.gz → 3.1.1__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.0.2 → libsast-3.1.1}/PKG-INFO +2 -2
- {libsast-3.0.2 → libsast-3.1.1}/libsast/__init__.py +1 -1
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/choice_matcher.py +23 -15
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/pattern_matcher.py +19 -7
- libsast-3.1.1/libsast/core_sgrep/helpers.py +29 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_sgrep/semantic_sgrep.py +4 -2
- {libsast-3.0.2 → libsast-3.1.1}/pyproject.toml +2 -2
- libsast-3.0.2/libsast/core_sgrep/helpers.py +0 -68
- {libsast-3.0.2 → libsast-3.1.1}/LICENSE +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/README.md +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/__main__.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/common.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/__init__.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/helpers.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/matchers.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/core_sgrep/__init__.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/exceptions.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/scanner.py +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/cwe.yaml +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_masvs.yaml +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_mobile_top10_2016.yaml +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_mobile_top10_2024.yaml +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_web_top10_2017.yaml +0 -0
- {libsast-3.0.2 → libsast-3.1.1}/libsast/standards.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libsast
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.1
|
|
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
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Security
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
23
|
Requires-Dist: pyyaml (>=6.0)
|
|
24
24
|
Requires-Dist: requests
|
|
25
|
-
Requires-Dist: semgrep (==
|
|
25
|
+
Requires-Dist: semgrep (==1.86.0) ; sys_platform != "win32"
|
|
26
26
|
Project-URL: Bug Tracker, https://github.com/ajinabraham/libsast/issues
|
|
27
27
|
Project-URL: Documentation, https://github.com/ajinabraham/libsast/blob/master/README.md
|
|
28
28
|
Project-URL: Repository, https://github.com/ajinabraham/libsast
|
|
@@ -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.
|
|
15
|
+
__version__ = '3.1.1'
|
|
16
16
|
__version_info__ = tuple(int(i) for i in __version__.split('.'))
|
|
17
17
|
__all__ = [
|
|
18
18
|
'Scanner',
|
|
@@ -31,14 +31,18 @@ class ChoiceMatcher:
|
|
|
31
31
|
|
|
32
32
|
def scan(self, paths: list) -> dict:
|
|
33
33
|
"""Scan file(s) or directory per rule."""
|
|
34
|
-
if not (self.scan_rules and paths):
|
|
35
|
-
return
|
|
36
|
-
self.validate_rules()
|
|
37
|
-
|
|
38
34
|
if self.show_progress:
|
|
39
35
|
pbar = common.ProgressBar('Choice Match', len(self.scan_rules))
|
|
40
36
|
self.scan_rules = pbar.progress_loop(self.scan_rules)
|
|
41
37
|
|
|
38
|
+
file_contents = self.read_file_contents(paths)
|
|
39
|
+
return self.regex_scan(file_contents)
|
|
40
|
+
|
|
41
|
+
def read_file_contents(self, paths: list) -> list:
|
|
42
|
+
"""Load file(s) content."""
|
|
43
|
+
if not (self.scan_rules and paths):
|
|
44
|
+
return
|
|
45
|
+
self.validate_rules()
|
|
42
46
|
choice_args = []
|
|
43
47
|
for rule in self.scan_rules:
|
|
44
48
|
scan_paths = paths
|
|
@@ -46,25 +50,29 @@ class ChoiceMatcher:
|
|
|
46
50
|
# Scan only alternative path
|
|
47
51
|
scan_paths = [Path(self.alternative_path)]
|
|
48
52
|
choice_args.append((scan_paths, rule))
|
|
53
|
+
if not choice_args:
|
|
54
|
+
return []
|
|
49
55
|
|
|
50
|
-
# Use ThreadPoolExecutor for
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
max_workers=self.cpu) as cpu_executor:
|
|
56
|
+
# Use ThreadPoolExecutor for file reading
|
|
57
|
+
with ThreadPoolExecutor() as io_executor:
|
|
58
|
+
# Submit file reading tasks and wait for results
|
|
54
59
|
futures = []
|
|
55
60
|
for args_tuple in choice_args:
|
|
56
|
-
# Submit each read task and store the future along with the args
|
|
57
61
|
future = io_executor.submit(
|
|
58
62
|
self._read_file_contents, args_tuple)
|
|
59
|
-
futures.append(
|
|
63
|
+
futures.append(future)
|
|
64
|
+
return [future.result() for future in futures]
|
|
65
|
+
|
|
66
|
+
def regex_scan(self, file_contents) -> list:
|
|
67
|
+
"""Process regex matches on the file contents."""
|
|
68
|
+
# Use ProcessPoolExecutor for regex processing
|
|
69
|
+
with ProcessPoolExecutor(max_workers=self.cpu) as cpu_executor:
|
|
60
70
|
|
|
61
71
|
results = []
|
|
62
|
-
for
|
|
63
|
-
|
|
64
|
-
# This will block until the file reading is done
|
|
65
|
-
# Process the file contents with ProcessPoolExecutor
|
|
72
|
+
for content in file_contents:
|
|
73
|
+
# Process Choice Matcher on the file contents
|
|
66
74
|
process_future = cpu_executor.submit(
|
|
67
|
-
self.choice_matcher,
|
|
75
|
+
self.choice_matcher, content)
|
|
68
76
|
results.append(process_future.result())
|
|
69
77
|
|
|
70
78
|
self.add_finding(results)
|
|
@@ -31,27 +31,39 @@ class PatternMatcher:
|
|
|
31
31
|
|
|
32
32
|
def scan(self, paths: list) -> dict:
|
|
33
33
|
"""Scan file(s) or directory."""
|
|
34
|
-
if not (self.scan_rules and paths):
|
|
35
|
-
return
|
|
36
|
-
self.validate_rules()
|
|
37
|
-
|
|
38
34
|
if self.show_progress:
|
|
39
35
|
pbar = common.ProgressBar('Pattern Match', len(paths))
|
|
40
36
|
paths = pbar.progress_loop(paths)
|
|
41
37
|
|
|
38
|
+
file_contents = self.read_file_contents(paths)
|
|
39
|
+
return self.regex_scan(file_contents)
|
|
40
|
+
|
|
41
|
+
def read_file_contents(self, paths: list) -> list:
|
|
42
|
+
"""Load file(s) content."""
|
|
43
|
+
if not (self.scan_rules and paths):
|
|
44
|
+
return
|
|
45
|
+
self.validate_rules()
|
|
46
|
+
|
|
42
47
|
# Filter files by extension and size, prepare list for processing
|
|
43
48
|
files_to_scan = {
|
|
44
49
|
sfile for sfile in paths
|
|
45
50
|
if is_file_valid(sfile, self.exts, 5)
|
|
46
51
|
}
|
|
52
|
+
if not files_to_scan:
|
|
53
|
+
return []
|
|
47
54
|
|
|
48
|
-
# Use a ThreadPool for file reading
|
|
49
|
-
with ThreadPoolExecutor() as io_executor
|
|
50
|
-
max_workers=self.cpu) as cpu_executor:
|
|
55
|
+
# Use a ThreadPool for file reading
|
|
56
|
+
with ThreadPoolExecutor() as io_executor:
|
|
51
57
|
|
|
52
58
|
# Read all files
|
|
53
59
|
file_contents = list(io_executor.map(
|
|
54
60
|
self._read_file_content, files_to_scan))
|
|
61
|
+
return file_contents
|
|
62
|
+
|
|
63
|
+
def regex_scan(self, file_contents: list) -> dict:
|
|
64
|
+
"""Scan file(s) content."""
|
|
65
|
+
# Use a ProcessPool for CPU-bound regex
|
|
66
|
+
with ProcessPoolExecutor(max_workers=self.cpu) as cpu_executor:
|
|
55
67
|
|
|
56
68
|
# Run regex on file data
|
|
57
69
|
results = cpu_executor.map(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# -*- coding: utf_8 -*-
|
|
2
|
+
"""Semantic Grep Helpers."""
|
|
3
|
+
import json
|
|
4
|
+
import platform
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def invoke_semgrep(paths, scan_rules):
|
|
9
|
+
if platform.system() == 'Windows':
|
|
10
|
+
return None
|
|
11
|
+
ps = [pt.as_posix() for pt in paths]
|
|
12
|
+
command = [
|
|
13
|
+
'semgrep',
|
|
14
|
+
'--metrics=off',
|
|
15
|
+
'--no-rewrite-rule-ids',
|
|
16
|
+
'--json',
|
|
17
|
+
'-q',
|
|
18
|
+
'--config',
|
|
19
|
+
scan_rules,
|
|
20
|
+
*ps,
|
|
21
|
+
]
|
|
22
|
+
try:
|
|
23
|
+
result = subprocess.run(command, capture_output=True, text=True, check=True)
|
|
24
|
+
return json.loads(result.stdout)
|
|
25
|
+
except subprocess.CalledProcessError as e:
|
|
26
|
+
try:
|
|
27
|
+
return json.loads(e.output)
|
|
28
|
+
except json.JSONDecodeError:
|
|
29
|
+
return {'errors': e.output}
|
|
@@ -43,9 +43,11 @@ class SemanticGrep:
|
|
|
43
43
|
|
|
44
44
|
def format_output(self, results):
|
|
45
45
|
"""Format sgrep results."""
|
|
46
|
-
errs =
|
|
46
|
+
errs = results.get('errors')
|
|
47
47
|
if errs:
|
|
48
48
|
self.findings['errors'] = errs
|
|
49
|
+
if not results.get('results'):
|
|
50
|
+
return
|
|
49
51
|
smatches = self.findings['matches']
|
|
50
52
|
for find in results['results']:
|
|
51
53
|
file_details = {
|
|
@@ -54,7 +56,7 @@ class SemanticGrep:
|
|
|
54
56
|
'match_lines': (find['start']['line'], find['end']['line']),
|
|
55
57
|
'match_string': find['extra']['lines'],
|
|
56
58
|
}
|
|
57
|
-
rule_id = find['check_id']
|
|
59
|
+
rule_id = find['check_id']
|
|
58
60
|
if rule_id in smatches:
|
|
59
61
|
smatches[rule_id]['files'].append(file_details)
|
|
60
62
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "libsast"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.1.1"
|
|
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>"]
|
|
@@ -26,7 +26,7 @@ libsast = "libsast.__main__:main"
|
|
|
26
26
|
python = "^3.8"
|
|
27
27
|
requests = "*"
|
|
28
28
|
pyyaml = ">=6.0"
|
|
29
|
-
semgrep = {version = "
|
|
29
|
+
semgrep = {version = "1.86.0", markers = "sys_platform != 'win32'"}
|
|
30
30
|
|
|
31
31
|
[tool.poetry.group.dev.dependencies]
|
|
32
32
|
bandit = "*"
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf_8 -*-
|
|
2
|
-
"""Semantic Grep Helpers."""
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
import platform
|
|
6
|
-
import multiprocessing
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def invoke_semgrep(paths, scan_rules, **kwargs):
|
|
10
|
-
"""Call Semgrep."""
|
|
11
|
-
if platform.system() == 'Windows':
|
|
12
|
-
return None
|
|
13
|
-
from semgrep import semgrep_main
|
|
14
|
-
from semgrep.state import get_state
|
|
15
|
-
from semgrep.constants import OutputFormat
|
|
16
|
-
from semgrep.output import OutputHandler, OutputSettings
|
|
17
|
-
try:
|
|
18
|
-
cpu_count = multiprocessing.cpu_count()
|
|
19
|
-
except NotImplementedError:
|
|
20
|
-
cpu_count = 1 # CPU count is not implemented on Windows
|
|
21
|
-
# Semgrep output formatting
|
|
22
|
-
state = get_state()
|
|
23
|
-
state.terminal.configure(
|
|
24
|
-
verbose=False,
|
|
25
|
-
debug=False,
|
|
26
|
-
quiet=True,
|
|
27
|
-
force_color=False,
|
|
28
|
-
)
|
|
29
|
-
logging.getLogger('semgrep').propagate = False
|
|
30
|
-
output_settings = OutputSettings(
|
|
31
|
-
output_format=OutputFormat.JSON,
|
|
32
|
-
output_destination=None,
|
|
33
|
-
output_per_finding_max_lines_limit=None,
|
|
34
|
-
output_per_line_max_chars_limit=None,
|
|
35
|
-
error_on_findings=False,
|
|
36
|
-
verbose_errors=False,
|
|
37
|
-
strict=False,
|
|
38
|
-
timeout_threshold=3,
|
|
39
|
-
)
|
|
40
|
-
output_handler = OutputHandler(output_settings)
|
|
41
|
-
(
|
|
42
|
-
filtered_matches_by_rule,
|
|
43
|
-
_,
|
|
44
|
-
_,
|
|
45
|
-
_,
|
|
46
|
-
_,
|
|
47
|
-
_,
|
|
48
|
-
_,
|
|
49
|
-
_,
|
|
50
|
-
_,
|
|
51
|
-
_,
|
|
52
|
-
_,
|
|
53
|
-
_,
|
|
54
|
-
) = semgrep_main.main(
|
|
55
|
-
output_handler=output_handler,
|
|
56
|
-
target=[pt.as_posix() for pt in paths],
|
|
57
|
-
jobs=cpu_count,
|
|
58
|
-
pattern=None,
|
|
59
|
-
lang=None,
|
|
60
|
-
configs=[scan_rules],
|
|
61
|
-
timeout=5,
|
|
62
|
-
timeout_threshold=3,
|
|
63
|
-
**kwargs,
|
|
64
|
-
)
|
|
65
|
-
output_handler.rule_matches = [
|
|
66
|
-
m for ms in filtered_matches_by_rule.values() for m in ms
|
|
67
|
-
]
|
|
68
|
-
return json.loads(output_handler._build_output())
|
|
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
|