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.
Files changed (23) hide show
  1. {libsast-3.1.0 → libsast-3.1.2}/PKG-INFO +2 -1
  2. {libsast-3.1.0 → libsast-3.1.2}/libsast/__init__.py +1 -1
  3. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/choice_matcher.py +33 -19
  4. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/pattern_matcher.py +35 -13
  5. {libsast-3.1.0 → libsast-3.1.2}/libsast/scanner.py +1 -0
  6. {libsast-3.1.0 → libsast-3.1.2}/pyproject.toml +2 -1
  7. {libsast-3.1.0 → libsast-3.1.2}/LICENSE +0 -0
  8. {libsast-3.1.0 → libsast-3.1.2}/README.md +0 -0
  9. {libsast-3.1.0 → libsast-3.1.2}/libsast/__main__.py +0 -0
  10. {libsast-3.1.0 → libsast-3.1.2}/libsast/common.py +0 -0
  11. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/__init__.py +0 -0
  12. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/helpers.py +0 -0
  13. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_matcher/matchers.py +0 -0
  14. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/__init__.py +0 -0
  15. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/helpers.py +0 -0
  16. {libsast-3.1.0 → libsast-3.1.2}/libsast/core_sgrep/semantic_sgrep.py +0 -0
  17. {libsast-3.1.0 → libsast-3.1.2}/libsast/exceptions.py +0 -0
  18. {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/cwe.yaml +0 -0
  19. {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_masvs.yaml +0 -0
  20. {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_mobile_top10_2016.yaml +0 -0
  21. {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_mobile_top10_2024.yaml +0 -0
  22. {libsast-3.1.0 → libsast-3.1.2}/libsast/standards/owasp_web_top10_2017.yaml +0 -0
  23. {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.0
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.0'
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 reading file contents and
51
- # ProcessPoolExecutor for processing regex
52
- with ThreadPoolExecutor() as io_executor, ProcessPoolExecutor(
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((future, args_tuple))
60
-
61
- 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
66
- process_future = cpu_executor.submit(
67
- self.choice_matcher, file_contents)
68
- results.append(process_future.result())
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, and ProcessPool for CPU-bound regex
49
- with ThreadPoolExecutor() as io_executor, ProcessPoolExecutor(
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
- # Run regex on file data
57
- results = cpu_executor.map(
58
- self.pattern_matcher,
59
- file_contents,
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)
@@ -26,6 +26,7 @@ class Scanner:
26
26
  'ignore_paths': [],
27
27
  'show_progress': False,
28
28
  'cpu_core': 1,
29
+ 'queue': False,
29
30
  # Overwrite with options from invocation
30
31
  **(options or {}),
31
32
  }
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "libsast"
3
- version = "3.1.0"
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