owasp-depscan 5.4.8__py3-none-any.whl → 6.0.0a2__py3-none-any.whl

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.

Potentially problematic release.


This version of owasp-depscan might be problematic. Click here for more details.

Files changed (34) hide show
  1. depscan/__init__.py +8 -0
  2. depscan/cli.py +719 -827
  3. depscan/cli_options.py +302 -0
  4. depscan/lib/audit.py +3 -1
  5. depscan/lib/bom.py +390 -288
  6. depscan/lib/config.py +86 -337
  7. depscan/lib/explainer.py +363 -98
  8. depscan/lib/license.py +11 -10
  9. depscan/lib/logger.py +65 -17
  10. depscan/lib/package_query/__init__.py +0 -0
  11. depscan/lib/package_query/cargo_pkg.py +124 -0
  12. depscan/lib/package_query/metadata.py +170 -0
  13. depscan/lib/package_query/npm_pkg.py +345 -0
  14. depscan/lib/package_query/pkg_query.py +195 -0
  15. depscan/lib/package_query/pypi_pkg.py +113 -0
  16. depscan/lib/tomlparse.py +116 -0
  17. depscan/lib/utils.py +34 -188
  18. owasp_depscan-6.0.0a2.dist-info/METADATA +390 -0
  19. {owasp_depscan-5.4.8.dist-info → owasp_depscan-6.0.0a2.dist-info}/RECORD +28 -25
  20. {owasp_depscan-5.4.8.dist-info → owasp_depscan-6.0.0a2.dist-info}/WHEEL +1 -1
  21. vendor/choosealicense.com/_licenses/cern-ohl-p-2.0.txt +1 -1
  22. vendor/choosealicense.com/_licenses/cern-ohl-s-2.0.txt +1 -1
  23. vendor/choosealicense.com/_licenses/cern-ohl-w-2.0.txt +2 -2
  24. vendor/choosealicense.com/_licenses/mit-0.txt +1 -1
  25. vendor/spdx/json/licenses.json +904 -677
  26. depscan/lib/analysis.py +0 -1550
  27. depscan/lib/csaf.py +0 -1860
  28. depscan/lib/normalize.py +0 -312
  29. depscan/lib/orasclient.py +0 -142
  30. depscan/lib/pkg_query.py +0 -532
  31. owasp_depscan-5.4.8.dist-info/METADATA +0 -580
  32. {owasp_depscan-5.4.8.dist-info → owasp_depscan-6.0.0a2.dist-info}/entry_points.txt +0 -0
  33. {owasp_depscan-5.4.8.dist-info → owasp_depscan-6.0.0a2.dist-info/licenses}/LICENSE +0 -0
  34. {owasp_depscan-5.4.8.dist-info → owasp_depscan-6.0.0a2.dist-info}/top_level.txt +0 -0
depscan/lib/explainer.py CHANGED
@@ -1,69 +1,186 @@
1
- import json
2
- import os
3
1
  import re
4
-
2
+ import glob
3
+ from collections import defaultdict
4
+ from custom_json_diff.lib.utils import json_load
5
5
  from rich import box
6
6
  from rich.markdown import Markdown
7
7
  from rich.table import Table
8
8
  from rich.tree import Tree
9
9
 
10
- from depscan.lib.config import max_purl_per_flow, max_reachable_explanations
11
- from depscan.lib.logger import console
10
+ from depscan.lib.config import (
11
+ COMMON_CHECK_TAGS,
12
+ max_purl_per_flow,
13
+ max_reachable_explanations,
14
+ max_purls_reachable_explanations,
15
+ max_source_reachable_explanations,
16
+ max_sink_reachable_explanations,
17
+ )
18
+ from depscan.lib.logger import console, LOG
12
19
 
13
20
 
14
- def explain(
15
- project_type,
16
- src_dir,
17
- reachables_slices_file,
18
- vdr_file,
19
- pkg_vulnerabilities,
20
- pkg_group_rows,
21
- direct_purls,
22
- reached_purls,
23
- ):
21
+ def explain(project_type, src_dir, bom_dir, vdr_file, vdr_result, explanation_mode):
24
22
  """
25
- Explain the analysis and findings
23
+ Explain the analysis and findings based on the explanation mode.
26
24
 
27
25
  :param project_type: Project type
28
26
  :param src_dir: Source directory
29
- :param reachables_slices_file: Reachables slices file
30
- :param vdr_file: VDR file from the summariser
31
- :param pkg_vulnerabilities: Vulnerabilities from the analysis
32
- :param pkg_group_rows: Prioritized list of purls
33
- :param direct_purls: Dict containing packages used directly
34
- :param reached_purls: Dict containing packages identified via reachables slicing
27
+ :param bom_dir: BOM directory
28
+ :param vdr_file: VDR file
29
+ :param vdr_result: VDR Result
30
+ :param explanation_mode: Explanation mode
35
31
  """
36
- if (
37
- not reachables_slices_file
38
- and src_dir
39
- and os.path.exists(os.path.join(src_dir, "reachables.slices.json"))
32
+ pattern_methods = {}
33
+ has_any_explanation = False
34
+ has_any_crypto_flows = False
35
+ slices_files = glob.glob(f"{bom_dir}/**/*reachables.slices.json", recursive=True)
36
+ openapi_spec_files = None
37
+ # Should we explain the endpoints and Code Hotspots
38
+ if explanation_mode in (
39
+ "Endpoints",
40
+ "EndpointsAndReachables",
40
41
  ):
41
- reachables_slices_file = os.path.join(src_dir, "reachables.slices.json")
42
- if reachables_slices_file:
43
- with open(reachables_slices_file, "r", encoding="utf-8") as f:
44
- reachables_data = json.load(f)
45
- if reachables_data and reachables_data.get("reachables"):
42
+ openapi_spec_files = glob.glob(f"{bom_dir}/*openapi*.json", recursive=False)
43
+ if not openapi_spec_files:
44
+ openapi_spec_files = glob.glob(f"{src_dir}/*openapi*.json", recursive=False)
45
+ if openapi_spec_files:
46
+ rsection = Markdown("""## Service Endpoints
47
+
48
+ The following endpoints and code hotspots were identified by depscan. Verify that proper authentication and authorization mechanisms are in place to secure them.""")
49
+ console.print(rsection)
50
+ for ospec in openapi_spec_files:
51
+ pattern_methods = print_endpoints(ospec)
52
+ # Return early for endpoints only explanations
53
+ if explanation_mode in ("Endpoints",):
54
+ return
55
+ section_title = (
56
+ "Non-Reachable Flows"
57
+ if explanation_mode in ("NonReachables",)
58
+ else "Reachable Flows"
59
+ )
60
+ for sf in slices_files:
61
+ if (reachables_data := json_load(sf, log=LOG)) and reachables_data.get(
62
+ "reachables"
63
+ ):
64
+ if explanation_mode in ("NonReachables",):
46
65
  rsection = Markdown(
47
- """## Reachable Flows
66
+ f"""## {section_title}
48
67
 
49
- Below are some reachable flows identified by depscan. Use the provided tips to improve the securability of your application.
68
+ Below are several data flows deemed safe and non-reachable. Use the provided tips to confirm this assessment.
50
69
  """
51
70
  )
52
- console.print(rsection)
53
- explain_reachables(
54
- reachables_data, pkg_group_rows, project_type
71
+ elif pattern_methods:
72
+ rsection = Markdown(
73
+ f"""## {section_title}
74
+
75
+ Below are some reachable flows, including those accessible via endpoints, identified by depscan. Use the generated OpenAPI specification to evaluate these endpoints for vulnerabilities and risk.
76
+ """
77
+ )
78
+ else:
79
+ rsection = Markdown(
80
+ f"""## {section_title}
81
+
82
+ Below are several data flows identified by depscan, including reachable ones. Use the tips provided to strengthen your application’s security posture.
83
+ """
55
84
  )
85
+ has_explanation, has_crypto_flows, tips = explain_reachables(
86
+ explanation_mode,
87
+ reachables_data,
88
+ project_type,
89
+ vdr_result,
90
+ rsection if not has_any_explanation else None,
91
+ )
92
+ if not has_any_explanation and has_explanation:
93
+ has_any_explanation = True
94
+ if not has_any_crypto_flows and has_crypto_flows:
95
+ has_any_crypto_flows = True
96
+
97
+
98
+ def _track_usage_targets(usage_targets, usages_object):
99
+ for k, v in usages_object.items():
100
+ for file, lines in v.items():
101
+ for l in lines:
102
+ usage_targets.add(f"{file}#{l}")
56
103
 
57
104
 
58
- def explain_reachables(reachables, pkg_group_rows, project_type):
105
+ def print_endpoints(ospec):
106
+ if not ospec:
107
+ return
108
+ paths = json_load(ospec).get("paths") or {}
109
+ pattern_methods = defaultdict(list)
110
+ pattern_usage_targets = defaultdict(set)
111
+ for pattern, path_obj in paths.items():
112
+ usage_targets = set()
113
+ http_method_added = False
114
+ for k, v in path_obj.items():
115
+ if k == "parameters":
116
+ continue
117
+ # Java, JavaScript, Python etc
118
+ if k == "x-atom-usages":
119
+ _track_usage_targets(usage_targets, v)
120
+ continue
121
+ if isinstance(v, dict) and v.get("x-atom-usages"):
122
+ _track_usage_targets(usage_targets, v.get("x-atom-usages"))
123
+ pattern_methods[pattern].append(k)
124
+ http_method_added = True
125
+ pattern_usage_targets[pattern] = usage_targets
126
+ # We see an endpoint, but do not know the HTTP methods.
127
+ # Let's track them as empty
128
+ if not http_method_added and usage_targets:
129
+ pattern_methods[pattern].append("")
130
+ caption = ""
131
+ if pattern_methods:
132
+ caption = f"Identified Endpoints: {len(pattern_methods.keys())}"
133
+ rtable = Table(
134
+ box=box.DOUBLE_EDGE,
135
+ show_lines=True,
136
+ title="Endpoints",
137
+ caption=caption,
138
+ )
139
+ for c in ("URL Pattern", "HTTP Methods", "Code Hotspots"):
140
+ rtable.add_column(header=c, vertical="top")
141
+ for k, v in pattern_methods.items():
142
+ v.sort()
143
+ sorted_areas = list(pattern_usage_targets[k])
144
+ sorted_areas.sort()
145
+ rtable.add_row(k, ("\n".join(v)).upper(), "\n".join(sorted_areas))
146
+ if pattern_methods:
147
+ console.print()
148
+ console.print(rtable)
149
+ return pattern_methods
150
+
151
+
152
+ def is_cpp_flow(flows):
153
+ if not flows:
154
+ return False
155
+ attempts = 0
156
+ for idx, aflow in enumerate(flows):
157
+ if aflow.get("parentFileName", "").endswith(".c") or aflow.get(
158
+ "parentFileName", ""
159
+ ).endswith(".cpp"):
160
+ return True
161
+ attempts += 1
162
+ if attempts > 3:
163
+ return False
164
+ return False
165
+
166
+
167
+ def explain_reachables(
168
+ explanation_mode, reachables, project_type, vdr_result, header_section=None
169
+ ):
59
170
  """"""
60
171
  reachable_explanations = 0
61
172
  checked_flows = 0
173
+ has_crypto_flows = False
174
+ purls_reachable_explanations = defaultdict(int)
175
+ source_reachable_explanations = defaultdict(int)
176
+ sink_reachable_explanations = defaultdict(int)
177
+ has_explanation = False
178
+ header_shown = False
62
179
  for areach in reachables.get("reachables", []):
63
180
  if (
64
181
  not areach.get("flows")
65
182
  or len(areach.get("flows")) < 2
66
- or not areach.get("purls")
183
+ or (not areach.get("purls") and not is_cpp_flow(areach.get("flows")))
67
184
  ):
68
185
  continue
69
186
  # Focus only on the prioritized list if available
@@ -74,11 +191,55 @@ def explain_reachables(reachables, pkg_group_rows, project_type):
74
191
  # is_prioritized = True
75
192
  # if not is_prioritized:
76
193
  # continue
77
- flow_tree, comment, source_sink_desc, has_check_tag = explain_flows(
78
- areach.get("flows"), areach.get("purls"), project_type
194
+ (
195
+ flow_tree,
196
+ comment,
197
+ source_sink_desc,
198
+ source_code_str,
199
+ sink_code_str,
200
+ has_check_tag,
201
+ is_endpoint_reachable,
202
+ is_crypto_flow,
203
+ ) = explain_flows(
204
+ explanation_mode,
205
+ areach.get("flows"),
206
+ areach.get("purls"),
207
+ project_type,
208
+ vdr_result,
79
209
  )
80
210
  if not source_sink_desc or not flow_tree:
81
211
  continue
212
+ # In non-reachables mode, we are not interested in reachable flows.
213
+ if (
214
+ explanation_mode
215
+ and explanation_mode in ("NonReachables",)
216
+ and not has_check_tag
217
+ ):
218
+ continue
219
+ if (
220
+ source_code_str
221
+ and source_reachable_explanations[source_code_str] + 1
222
+ > max_source_reachable_explanations
223
+ ):
224
+ continue
225
+ if (
226
+ sink_code_str
227
+ and sink_reachable_explanations[sink_code_str] + 1
228
+ > max_sink_reachable_explanations
229
+ ):
230
+ continue
231
+ purls_str = ",".join(sorted(areach.get("purls", [])))
232
+ if (
233
+ purls_str
234
+ and purls_reachable_explanations[purls_str] + 1
235
+ > max_purls_reachable_explanations
236
+ ):
237
+ continue
238
+ if not has_explanation:
239
+ has_explanation = True
240
+ # Did we find any crypto flows
241
+ if is_crypto_flow and not has_crypto_flows:
242
+ has_crypto_flows = True
82
243
  rtable = Table(
83
244
  box=box.DOUBLE_EDGE,
84
245
  show_lines=True,
@@ -90,31 +251,74 @@ def explain_reachables(reachables, pkg_group_rows, project_type):
90
251
  )
91
252
  rtable.add_column(header="Flow", vertical="top")
92
253
  rtable.add_row(flow_tree)
254
+ # Print the header first in case we haven't
255
+ if not header_shown and header_section:
256
+ console.print()
257
+ console.print(header_section)
258
+ header_shown = True
93
259
  console.print()
94
260
  console.print(rtable)
95
261
  reachable_explanations += 1
262
+ if purls_str:
263
+ purls_reachable_explanations[purls_str] += 1
264
+ if source_code_str:
265
+ source_reachable_explanations[source_code_str] += 1
266
+ if sink_code_str:
267
+ sink_reachable_explanations[sink_code_str] += 1
96
268
  if has_check_tag:
97
269
  checked_flows += 1
98
270
  if reachable_explanations + 1 > max_reachable_explanations:
99
271
  break
100
- if reachable_explanations:
101
- tips = """## Secure Design Tips"""
102
-
103
- if checked_flows:
272
+ tips = """## Secure Design Tips"""
273
+ if explanation_mode in ("NonReachables",):
274
+ tips += """
275
+ - Automate tests (including fuzzing) to verify validation, sanitization, encoding, and encryption.
276
+ - Align the implementation with the original architecture and threat models to ensure security compliance.
277
+ - Extract reusable methods into a shared library for organization-wide use.
278
+ """
279
+ elif has_explanation:
280
+ if has_crypto_flows:
281
+ tips += """
282
+ - Generate a Cryptographic BOM with cdxgen and monitor it in Dependency-Track.
283
+ """
284
+ elif checked_flows:
285
+ tips += """
286
+ - Review the validation and sanitization methods used in the application.
287
+ - To enhance the security posture, implement a common validation middleware.
288
+ """
289
+ elif purls_reachable_explanations:
104
290
  tips += """
105
- - Review the detected validation/sanitization methods in the application.
106
- - To improve the security posture, implement a common validation middleware.
291
+ - Consider implementing a common validation and sanitization library to reduce the risk of exploitability.
107
292
  """
108
293
  else:
109
294
  tips += """
110
- - Consider implementing a common validation/sanitization library to reduce the exploitability risk.
295
+ - Enhance your unit and integration tests to cover the flows listed above.
296
+ - Continuously fuzz the parser and validation functions with diverse payloads.
111
297
  """
298
+ if tips:
112
299
  rsection = Markdown(tips)
113
300
  console.print(rsection)
301
+ return has_explanation, has_crypto_flows, tips
114
302
 
115
303
 
116
- def flow_to_source_sink(idx, flow, purls, project_type):
304
+ def flow_to_source_sink(idx, flow, purls, project_type, vdr_result):
117
305
  """ """
306
+ endpoint_reached_purls = {}
307
+ reached_services = {}
308
+ if vdr_result:
309
+ endpoint_reached_purls = vdr_result.endpoint_reached_purls
310
+ reached_services = vdr_result.reached_services
311
+ is_endpoint_reachable = False
312
+ possible_reachable_service = False
313
+ tags = flow.get("tags", [])
314
+ is_crypto_flow = "crypto" in tags or "crypto-generate" in tags
315
+ method_in_emoji = ":right_arrow_curving_left:"
316
+ for p in purls:
317
+ if endpoint_reached_purls and endpoint_reached_purls.get(p):
318
+ is_endpoint_reachable = True
319
+ method_in_emoji = ":heavy_large_circle: "
320
+ if reached_services and reached_services.get(p):
321
+ possible_reachable_service = True
118
322
  source_sink_desc = ""
119
323
  param_name = flow.get("name")
120
324
  method_str = "method"
@@ -130,14 +334,14 @@ def flow_to_source_sink(idx, flow, purls, project_type):
130
334
  if parent_method in ("handleRequest",):
131
335
  method_str = f"handler {method_str}"
132
336
  elif parent_method in ("__construct", "__init"):
133
- method_str = f"constructor"
337
+ method_str = "constructor"
134
338
  elif project_type in ("php",) and parent_method.startswith("__"):
135
339
  method_str = f"magic {method_str}"
136
340
  if flow.get("label") == "METHOD_PARAMETER_IN":
137
341
  if param_name:
138
- source_sink_desc = f"""{param_str} [red]{param_name}[/red] :right_arrow_curving_left: to the {method_str} [bold]{parent_method}[/bold]"""
342
+ source_sink_desc = f"""{param_str} [red]{param_name}[/red] {method_in_emoji} to the {method_str} [bold]{parent_method}[/bold]"""
139
343
  else:
140
- source_sink_desc = f"""{method_str.capitalize()} [red]{parent_method}[/red] :right_arrow_curving_left:"""
344
+ source_sink_desc = f"""{method_str.capitalize()} [red]{parent_method}[/red] {method_in_emoji}"""
141
345
  elif flow.get("label") == "CALL" and flow.get("isExternal"):
142
346
  method_full_name = flow.get("fullName", "")
143
347
  if not method_full_name.startswith("<"):
@@ -146,9 +350,11 @@ def flow_to_source_sink(idx, flow, purls, project_type):
146
350
  source_sink_desc = flow.get("code").split("\n")[0]
147
351
  elif project_type not in ("java") and flow.get("label") == "IDENTIFIER":
148
352
  source_sink_desc = flow.get("code").split("\n")[0]
353
+ if source_sink_desc.endswith("("):
354
+ source_sink_desc = f":diamond_suit: {source_sink_desc})"
149
355
  # Try to understand the source a bit more
150
356
  if source_sink_desc.startswith("require("):
151
- source_sink_desc = "Flow starts from a module import"
357
+ source_sink_desc = "The flow originates from a module import."
152
358
  elif (
153
359
  ".use(" in source_sink_desc
154
360
  or ".subscribe(" in source_sink_desc
@@ -156,21 +362,41 @@ def flow_to_source_sink(idx, flow, purls, project_type):
156
362
  or ".emit(" in source_sink_desc
157
363
  or " => {" in source_sink_desc
158
364
  ):
159
- source_sink_desc = "Flow starts from a callback function"
365
+ source_sink_desc = "The flow originates from a callback function."
160
366
  elif (
161
- "middleware" in source_sink_desc.lower()
162
- or "route" in source_sink_desc.lower()
367
+ "middleware" in source_sink_desc.lower() or "route" in source_sink_desc.lower()
163
368
  ):
164
- source_sink_desc = "Flow starts from a middlware"
369
+ source_sink_desc = "The flow originates from middleware."
370
+ elif len(purls) == 0:
371
+ if tags:
372
+ source_sink_desc = (
373
+ f"{source_sink_desc} can be used to reach packages with tags `{tags}`"
374
+ )
165
375
  elif len(purls) == 1:
166
- source_sink_desc = (
167
- f"{source_sink_desc} can be used to reach this package."
168
- )
376
+ if is_endpoint_reachable:
377
+ source_sink_desc = f"{source_sink_desc} can be used to reach this package from certain endpoints."
378
+ elif source_sink_desc:
379
+ if is_crypto_flow:
380
+ source_sink_desc = "Reachable crypto-flow."
381
+ else:
382
+ source_sink_desc = "Reachable data-flow."
169
383
  else:
170
- source_sink_desc = (
171
- f"{source_sink_desc} can be used to reach {len(purls)} packages."
172
- )
173
- return source_sink_desc
384
+ if is_endpoint_reachable:
385
+ source_sink_desc = f"{source_sink_desc} can be used to reach {len(purls)} packages from certain endpoints."
386
+ else:
387
+ if source_sink_desc:
388
+ source_sink_desc = (
389
+ f"{source_sink_desc} can be used to reach {len(purls)} packages."
390
+ )
391
+ elif is_crypto_flow:
392
+ source_sink_desc = (
393
+ f"{len(purls)} packages reachable from this crypto-flow."
394
+ )
395
+ else:
396
+ source_sink_desc = (
397
+ f"{len(purls)} packages reachable from this data-flow."
398
+ )
399
+ return source_sink_desc, is_endpoint_reachable, is_crypto_flow
174
400
 
175
401
 
176
402
  def filter_tags(tags):
@@ -178,14 +404,30 @@ def filter_tags(tags):
178
404
  tags = [
179
405
  atag
180
406
  for atag in tags.split(", ")
181
- if atag
182
- not in ("RESOLVED_MEMBER", "UNKNOWN_METHOD", "UNKNOWN_TYPE_DECL")
407
+ if atag not in ("RESOLVED_MEMBER", "UNKNOWN_METHOD", "UNKNOWN_TYPE_DECL")
183
408
  ]
184
409
  return ", ".join(tags)
185
410
  return tags
186
411
 
187
412
 
188
- def flow_to_str(flow, project_type):
413
+ def is_filterable_code(project_type, code):
414
+ match project_type:
415
+ case "js" | "ts" | "javascript" | "typescript" | "bom":
416
+ for c in (
417
+ "console.log",
418
+ "thoughtLog(",
419
+ "_tmp_",
420
+ "LOG.debug(",
421
+ "options.get(",
422
+ "RET",
423
+ "this.",
424
+ ):
425
+ if code and code.startswith(c):
426
+ return True
427
+ return False
428
+
429
+
430
+ def flow_to_str(explanation_mode, flow, project_type):
189
431
  """"""
190
432
  has_check_tag = False
191
433
  file_loc = ""
@@ -194,60 +436,65 @@ def flow_to_str(flow, project_type):
194
436
  and flow.get("lineNumber")
195
437
  and not flow.get("parentFileName").startswith("unknown")
196
438
  ):
197
- file_loc = f'{flow.get("parentFileName").replace("src/main/java/", "").replace("src/main/scala/", "")}#{flow.get("lineNumber")} '
439
+ file_loc = f"{flow.get('parentFileName').replace('src/main/java/', '').replace('src/main/scala/', '')}#{flow.get('lineNumber')} "
198
440
  node_desc = flow.get("code").split("\n")[0]
441
+ if node_desc.endswith("("):
442
+ node_desc = f":diamond_suit: {node_desc})"
199
443
  tags = filter_tags(flow.get("tags"))
200
444
  if flow.get("label") == "METHOD_PARAMETER_IN":
201
445
  param_name = flow.get("name")
202
446
  if param_name == "this":
203
447
  param_name = ""
204
- node_desc = f'{flow.get("parentMethodName")}([red]{param_name}[/red]) :right_arrow_curving_left:'
448
+ node_desc = f"{flow.get('parentMethodName')}([red]{param_name}[/red]) :right_arrow_curving_left:"
205
449
  if tags:
206
- node_desc = (
207
- f"{node_desc}\n[bold]Tags:[/bold] [italic]{tags}[/italic]\n"
208
- )
450
+ node_desc = f"{node_desc}\n[bold]Tags:[/bold] [italic]{tags}[/italic]\n"
209
451
  elif flow.get("label") == "IDENTIFIER":
210
452
  if node_desc.startswith("<"):
211
453
  node_desc = flow.get("name")
212
454
  if tags:
213
- node_desc = (
214
- f"{node_desc}\n[bold]Tags:[/bold] [italic]{tags}[/italic]\n"
215
- )
216
- if tags:
217
- for ctag in (
218
- "validation",
219
- "encode",
220
- "encrypt",
221
- "sanitize",
222
- "authentication",
223
- "authorization",
224
- ):
455
+ node_desc = f"{node_desc}\n[bold]Tags:[/bold] [italic]{tags}[/italic]\n"
456
+ if tags and not is_filterable_code(project_type, node_desc):
457
+ for ctag in COMMON_CHECK_TAGS:
225
458
  if ctag in tags:
226
459
  has_check_tag = True
227
460
  break
228
461
  if has_check_tag:
229
- node_desc = f"[green]{node_desc}[/green]"
462
+ if explanation_mode in ("NonReachables",):
463
+ node_desc = f"[bold][green]{node_desc}[/green][/bold]"
464
+ else:
465
+ node_desc = f"[green]{node_desc}[/green]"
466
+ flow_str = (
467
+ f"""[gray37]{file_loc}[/gray37]{node_desc}"""
468
+ if not is_filterable_code(project_type, node_desc)
469
+ else ""
470
+ )
230
471
  return (
231
472
  file_loc,
232
- f"""[gray37]{file_loc}[/gray37]{node_desc}""",
473
+ flow_str,
474
+ node_desc,
233
475
  has_check_tag,
234
476
  )
235
477
 
236
478
 
237
- def explain_flows(flows, purls, project_type):
479
+ def explain_flows(explanation_mode, flows, purls, project_type, vdr_result):
238
480
  """"""
239
481
  tree = None
240
482
  comments = []
241
483
  if len(purls) > max_purl_per_flow:
242
484
  comments.append(
243
- ":exclamation_mark: Refactor this flow to reduce the number of external libraries used."
485
+ ":exclamation_mark: Refactor this flow to minimize the use of external libraries."
244
486
  )
245
- purls_str = "\n".join(purls)
246
- comments.append(f"[info]Reachable Packages:[/info]\n{purls_str}")
487
+ if purls:
488
+ purls_str = "\n".join(purls)
489
+ comments.append(f"[info]Reachable Packages:[/info]\n{purls_str}")
247
490
  added_flows = []
491
+ added_node_desc = []
248
492
  has_check_tag = False
249
493
  last_file_loc = None
250
494
  source_sink_desc = ""
495
+ last_code = ""
496
+ source_code_str = ""
497
+ sink_code_str = ""
251
498
  for idx, aflow in enumerate(flows):
252
499
  # For java, we are only interested in identifiers with tags to keep the flows simple to understand
253
500
  if (
@@ -256,28 +503,46 @@ def explain_flows(flows, purls, project_type):
256
503
  and not aflow.get("tags")
257
504
  ):
258
505
  continue
506
+ curr_code = aflow.get("code", "").split("\n")[0]
507
+ if idx == 0:
508
+ source_code_str = curr_code
509
+ if idx == len(flows):
510
+ sink_code_str = curr_code
511
+ if last_code and last_code == curr_code:
512
+ continue
513
+ last_code = curr_code
259
514
  if not source_sink_desc:
260
- source_sink_desc = flow_to_source_sink(
261
- idx, aflow, purls, project_type
515
+ source_sink_desc, is_endpoint_reachable, is_crypto_flow = (
516
+ flow_to_source_sink(idx, aflow, purls, project_type, vdr_result)
262
517
  )
263
- file_loc, flow_str, has_check_tag_flow = flow_to_str(
264
- aflow, project_type
518
+ file_loc, flow_str, node_desc, has_check_tag_flow = flow_to_str(
519
+ explanation_mode, aflow, project_type
265
520
  )
266
- if last_file_loc == file_loc:
521
+ if last_file_loc and last_file_loc == file_loc:
267
522
  continue
268
523
  last_file_loc = file_loc
269
- if flow_str in added_flows:
524
+ if flow_str in added_flows or node_desc in added_node_desc:
270
525
  continue
271
526
  added_flows.append(flow_str)
527
+ added_node_desc.append(node_desc)
272
528
  if not tree:
273
529
  tree = Tree(flow_str)
274
530
  else:
275
531
  tree.add(flow_str)
276
532
  if has_check_tag_flow:
277
533
  has_check_tag = True
278
- if has_check_tag:
534
+ if has_check_tag and explanation_mode not in ("NonReachables",):
279
535
  comments.insert(
280
536
  0,
281
- ":white_medium_small_square: Check if the mitigation(s) used in this flow is valid and appropriate for your security requirements.",
537
+ ":white_medium_small_square: Verify that the mitigation(s) used in this flow are valid and appropriate for your security requirements.",
282
538
  )
283
- return tree, "\n".join(comments), source_sink_desc, has_check_tag
539
+ return (
540
+ tree,
541
+ "\n".join(comments),
542
+ source_sink_desc,
543
+ source_code_str,
544
+ sink_code_str,
545
+ has_check_tag,
546
+ is_endpoint_reachable,
547
+ is_crypto_flow,
548
+ )
depscan/lib/license.py CHANGED
@@ -50,14 +50,15 @@ def bulk_lookup(license_dict, pkg_list):
50
50
  pkg_key = pkg["name"] + "@" + pkg["version"]
51
51
  if pkg.get("vendor"):
52
52
  pkg_key = pkg.get("vendor") + ":" + pkg["name"] + "@" + pkg["version"]
53
- for lic in pkg.get("licenses"):
54
- if lic == "X11":
55
- lic = "MIT"
56
- elif "MIT" in lic:
57
- lic = "MIT"
58
- curr_list = pkg_licenses.get(pkg_key, [])
59
- match_lic = license_dict.get(lic)
60
- if match_lic:
61
- curr_list.append(match_lic)
62
- pkg_licenses[pkg_key] = curr_list
53
+ if pkg.get("licenses"):
54
+ for lic in pkg.get("licenses"):
55
+ if lic == "X11":
56
+ lic = "MIT"
57
+ elif "MIT" in lic:
58
+ lic = "MIT"
59
+ curr_list = pkg_licenses.get(pkg_key, [])
60
+ match_lic = license_dict.get(lic)
61
+ if match_lic:
62
+ curr_list.append(match_lic)
63
+ pkg_licenses[pkg_key] = curr_list
63
64
  return pkg_licenses