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.
Files changed (24) hide show
  1. {libsast-3.0.2 → libsast-3.1.1}/PKG-INFO +2 -2
  2. {libsast-3.0.2 → libsast-3.1.1}/libsast/__init__.py +1 -1
  3. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/choice_matcher.py +23 -15
  4. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/pattern_matcher.py +19 -7
  5. libsast-3.1.1/libsast/core_sgrep/helpers.py +29 -0
  6. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_sgrep/semantic_sgrep.py +4 -2
  7. {libsast-3.0.2 → libsast-3.1.1}/pyproject.toml +2 -2
  8. libsast-3.0.2/libsast/core_sgrep/helpers.py +0 -68
  9. {libsast-3.0.2 → libsast-3.1.1}/LICENSE +0 -0
  10. {libsast-3.0.2 → libsast-3.1.1}/README.md +0 -0
  11. {libsast-3.0.2 → libsast-3.1.1}/libsast/__main__.py +0 -0
  12. {libsast-3.0.2 → libsast-3.1.1}/libsast/common.py +0 -0
  13. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/__init__.py +0 -0
  14. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/helpers.py +0 -0
  15. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_matcher/matchers.py +0 -0
  16. {libsast-3.0.2 → libsast-3.1.1}/libsast/core_sgrep/__init__.py +0 -0
  17. {libsast-3.0.2 → libsast-3.1.1}/libsast/exceptions.py +0 -0
  18. {libsast-3.0.2 → libsast-3.1.1}/libsast/scanner.py +0 -0
  19. {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/cwe.yaml +0 -0
  20. {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_masvs.yaml +0 -0
  21. {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_mobile_top10_2016.yaml +0 -0
  22. {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_mobile_top10_2024.yaml +0 -0
  23. {libsast-3.0.2 → libsast-3.1.1}/libsast/standards/owasp_web_top10_2017.yaml +0 -0
  24. {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.0.2
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 (==0.117.0) ; sys_platform != "win32"
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.0.2'
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 reading file contents and
51
- # ProcessPoolExecutor for processing regex
52
- with ThreadPoolExecutor() as io_executor, ProcessPoolExecutor(
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((future, args_tuple))
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 future, _ in futures:
63
- file_contents = future.result()
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, file_contents)
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, and ProcessPool for CPU-bound regex
49
- with ThreadPoolExecutor() as io_executor, ProcessPoolExecutor(
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 = self.findings.get('errors')
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'].rsplit('.', 1)[1]
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.0.2"
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 = "0.117.0", markers = "sys_platform != 'win32'"}
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