Skip to content

Commit a01ab19

Browse files
committed
add legal presets, file-based compliance artifacts
Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
1 parent 064fb7d commit a01ab19

2 files changed

Lines changed: 143 additions & 45 deletions

File tree

socketsecurity/config.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,16 @@ class CliConfig:
7979
enable_debug: bool = False
8080
allow_unverified: bool = False
8181
enable_json: bool = False
82+
json_file: Optional[str] = None
8283
enable_sarif: bool = False
8384
sarif_file: Optional[str] = None
8485
sarif_scope: str = "diff"
8586
sarif_grouping: str = "instance"
8687
sarif_reachability: str = "all"
8788
enable_gitlab_security: bool = False
8889
gitlab_security_file: Optional[str] = None
90+
summary_file: Optional[str] = None
91+
report_link_file: Optional[str] = None
8992
disable_overview: bool = False
9093
disable_security_issue: bool = False
9194
files: str = None
@@ -132,6 +135,7 @@ class CliConfig:
132135
reach_use_only_pregenerated_sboms: bool = False
133136
max_purl_batch_size: int = 5000
134137
enable_commit_status: bool = False
138+
legal: bool = False
135139
config_file: Optional[str] = None
136140

137141
@classmethod
@@ -189,13 +193,16 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
189193
'enable_diff': args.enable_diff,
190194
'allow_unverified': args.allow_unverified,
191195
'enable_json': args.enable_json,
196+
'json_file': args.json_file,
192197
'enable_sarif': args.enable_sarif,
193198
'sarif_file': args.sarif_file,
194199
'sarif_scope': args.sarif_scope,
195200
'sarif_grouping': args.sarif_grouping,
196201
'sarif_reachability': args.sarif_reachability,
197202
'enable_gitlab_security': args.enable_gitlab_security,
198203
'gitlab_security_file': args.gitlab_security_file,
204+
'summary_file': args.summary_file,
205+
'report_link_file': args.report_link_file,
199206
'disable_overview': args.disable_overview,
200207
'disable_security_issue': args.disable_security_issue,
201208
'files': args.files,
@@ -236,9 +243,23 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
236243
'reach_use_only_pregenerated_sboms': args.reach_use_only_pregenerated_sboms,
237244
'max_purl_batch_size': args.max_purl_batch_size,
238245
'enable_commit_status': args.enable_commit_status,
246+
'legal': args.legal,
239247
'config_file': args.config_file,
240248
'version': __version__
241249
}
250+
251+
if args.legal:
252+
config_args['generate_license'] = True
253+
if not config_args['json_file']:
254+
config_args['json_file'] = "socket-report.json"
255+
if not config_args['summary_file']:
256+
config_args['summary_file'] = "socket-summary.txt"
257+
if not config_args['report_link_file']:
258+
config_args['report_link_file'] = "socket-report-link.txt"
259+
if not config_args['sbom_file']:
260+
config_args['sbom_file'] = "socket-sbom.json"
261+
if config_args['license_file_name'] == "license_output.json":
262+
config_args['license_file_name'] = "socket-license.json"
242263
excluded_ecosystems = config_args["excluded_ecosystems"]
243264
if isinstance(excluded_ecosystems, list):
244265
config_args["excluded_ecosystems"] = excluded_ecosystems
@@ -560,6 +581,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
560581
action="store_true",
561582
help="Output in JSON format"
562583
)
584+
output_group.add_argument(
585+
"--json-file",
586+
dest="json_file",
587+
metavar="<path>",
588+
help="Output file path for JSON report"
589+
)
563590
output_group.add_argument(
564591
"--enable-sarif",
565592
dest="enable_sarif",
@@ -607,6 +634,18 @@ def create_argument_parser() -> argparse.ArgumentParser:
607634
default="gl-dependency-scanning-report.json",
608635
help="Output file path for GitLab Security report (default: gl-dependency-scanning-report.json)"
609636
)
637+
output_group.add_argument(
638+
"--summary-file",
639+
dest="summary_file",
640+
metavar="<path>",
641+
help="Output file path for a plain-text summary report"
642+
)
643+
output_group.add_argument(
644+
"--report-link-file",
645+
dest="report_link_file",
646+
metavar="<path>",
647+
help="Output file path for the Socket report link"
648+
)
610649
output_group.add_argument(
611650
"--disable-overview",
612651
dest="disable_overview",
@@ -723,6 +762,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
723762
action="store_true",
724763
help="Disable SSL certificate verification for API requests"
725764
)
765+
advanced_group.add_argument(
766+
"--legal",
767+
dest="legal",
768+
action="store_true",
769+
help="Enable legal/compliance-friendly defaults and file outputs"
770+
)
726771
config_group.add_argument(
727772
"--include-module-folders",
728773
dest="include_module_folders",

socketsecurity/output.py

Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def handle_output(self, diff_report: Diff) -> None:
9090
plugin_mgr = PluginManager({"slack": slack_config})
9191
plugin_mgr.send(diff_report, config=self.config)
9292

93+
self.save_json_file(diff_report, getattr(self.config, "json_file", None))
94+
self.save_summary_file(diff_report, getattr(self.config, "summary_file", None))
95+
self.save_report_link_file(diff_report, getattr(self.config, "report_link_file", None))
9396
self.save_sbom_file(diff_report, self.config.sbom_file)
9497

9598
def return_exit_code(self, diff_report: Diff) -> int:
@@ -107,50 +110,15 @@ def return_exit_code(self, diff_report: Diff) -> int:
107110

108111
def output_console_comments(self, diff_report: Diff, sbom_file_name: Optional[str] = None) -> None:
109112
"""Outputs formatted console comments"""
110-
selected_alerts = select_diff_alerts(diff_report, strict_blocking=self.config.strict_blocking)
111-
has_new_alerts = len(selected_alerts) > 0
112-
has_unchanged_alerts = (
113-
self.config.strict_blocking and
114-
hasattr(diff_report, 'unchanged_alerts') and
115-
len(diff_report.unchanged_alerts) > 0
116-
)
117-
118-
if not has_new_alerts and not has_unchanged_alerts:
119-
self.logger.info("No issues found")
120-
return
121-
122-
# Count blocking vs warning alerts
123-
new_blocking = sum(1 for issue in diff_report.new_alerts if issue.error)
124-
new_warning = sum(1 for issue in diff_report.new_alerts if issue.warn)
125-
126-
unchanged_blocking = 0
127-
unchanged_warning = 0
128-
if has_unchanged_alerts:
129-
unchanged_blocking = sum(1 for issue in diff_report.unchanged_alerts if issue.error)
130-
unchanged_warning = sum(1 for issue in diff_report.unchanged_alerts if issue.warn)
131-
132-
selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
133-
console_security_comment = Messages.create_console_security_alert_table(selected_diff)
134-
135-
# Build status message
136-
self.logger.info("Security issues detected by Socket Security:")
137-
if new_blocking > 0:
138-
self.logger.info(f" - NEW blocking issues: {new_blocking}")
139-
if new_warning > 0:
140-
self.logger.info(f" - NEW warning issues: {new_warning}")
141-
if unchanged_blocking > 0:
142-
self.logger.info(f" - EXISTING blocking issues: {unchanged_blocking} (causing failure due to --strict-blocking)")
143-
if unchanged_warning > 0:
144-
self.logger.info(f" - EXISTING warning issues: {unchanged_warning}")
145-
146-
self.logger.info(f"Diff Url: {diff_report.diff_url}")
147-
self.logger.info(f"\n{console_security_comment}")
113+
summary_text = self.build_summary_text(diff_report)
114+
for line in summary_text.splitlines():
115+
self.logger.info(line)
116+
if not summary_text.strip():
117+
self.logger.info("")
148118

149119
def output_console_json(self, diff_report: Diff, sbom_file_name: Optional[str] = None) -> None:
150120
"""Outputs JSON formatted results"""
151-
selected_alerts = select_diff_alerts(diff_report, strict_blocking=self.config.strict_blocking)
152-
selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
153-
console_security_comment = Messages.create_security_comment_json(selected_diff)
121+
console_security_comment = self.build_json_report(diff_report)
154122
self.save_sbom_file(diff_report, sbom_file_name)
155123
self.logger.info(json.dumps(console_security_comment))
156124

@@ -249,11 +217,96 @@ def save_sbom_file(self, diff_report: Diff, sbom_file_name: Optional[str] = None
249217
if not sbom_file_name or not diff_report.sbom:
250218
return
251219

252-
sbom_path = Path(sbom_file_name)
253-
sbom_path.parent.mkdir(parents=True, exist_ok=True)
220+
self.write_json_file(sbom_file_name, diff_report.sbom)
254221

255-
with open(sbom_path, "w") as f:
256-
json.dump(diff_report.sbom, f, indent=2)
222+
def build_summary_text(self, diff_report: Diff) -> str:
223+
"""Render the console summary text for stdout and file output."""
224+
selected_alerts = select_diff_alerts(diff_report, strict_blocking=self.config.strict_blocking)
225+
has_new_alerts = len(selected_alerts) > 0
226+
has_unchanged_alerts = (
227+
self.config.strict_blocking and
228+
hasattr(diff_report, 'unchanged_alerts') and
229+
len(diff_report.unchanged_alerts) > 0
230+
)
231+
232+
if not has_new_alerts and not has_unchanged_alerts:
233+
return "No issues found"
234+
235+
new_blocking = sum(1 for issue in diff_report.new_alerts if issue.error)
236+
new_warning = sum(1 for issue in diff_report.new_alerts if issue.warn)
237+
238+
unchanged_blocking = 0
239+
unchanged_warning = 0
240+
if has_unchanged_alerts:
241+
unchanged_blocking = sum(1 for issue in diff_report.unchanged_alerts if issue.error)
242+
unchanged_warning = sum(1 for issue in diff_report.unchanged_alerts if issue.warn)
243+
244+
selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
245+
console_security_comment = Messages.create_console_security_alert_table(selected_diff)
246+
247+
lines = ["Security issues detected by Socket Security:"]
248+
if new_blocking > 0:
249+
lines.append(f" - NEW blocking issues: {new_blocking}")
250+
if new_warning > 0:
251+
lines.append(f" - NEW warning issues: {new_warning}")
252+
if unchanged_blocking > 0:
253+
lines.append(
254+
f" - EXISTING blocking issues: {unchanged_blocking} (causing failure due to --strict-blocking)"
255+
)
256+
if unchanged_warning > 0:
257+
lines.append(f" - EXISTING warning issues: {unchanged_warning}")
258+
259+
report_link = getattr(diff_report, "report_url", "") or getattr(diff_report, "diff_url", "")
260+
lines.append(f"Diff Url: {report_link}")
261+
lines.append("")
262+
lines.append(str(console_security_comment))
263+
return "\n".join(lines)
264+
265+
def build_json_report(self, diff_report: Diff) -> dict:
266+
"""Build the JSON report payload for stdout and file output."""
267+
selected_alerts = select_diff_alerts(diff_report, strict_blocking=self.config.strict_blocking)
268+
selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
269+
report = Messages.create_security_comment_json(selected_diff)
270+
legal_flag = getattr(self.config, "legal", False)
271+
repo = getattr(self.config, "repo", None)
272+
branch = getattr(self.config, "branch", None)
273+
commit_sha = getattr(self.config, "commit_sha", None)
274+
report["report_url"] = getattr(diff_report, "report_url", None)
275+
report["repo"] = repo if isinstance(repo, str) or repo is None else None
276+
report["branch"] = branch if isinstance(branch, str) or branch is None else None
277+
report["commit_sha"] = commit_sha if isinstance(commit_sha, str) or commit_sha is None else None
278+
report["legal_mode"] = legal_flag if isinstance(legal_flag, bool) else False
279+
return report
280+
281+
def save_json_file(self, diff_report: Diff, json_file_name: Optional[str] = None) -> None:
282+
if not json_file_name:
283+
return
284+
self.write_json_file(json_file_name, self.build_json_report(diff_report))
285+
286+
def save_summary_file(self, diff_report: Diff, summary_file_name: Optional[str] = None) -> None:
287+
if not summary_file_name:
288+
return
289+
self.write_text_file(summary_file_name, self.build_summary_text(diff_report) + "\n")
290+
291+
def save_report_link_file(self, diff_report: Diff, report_link_file_name: Optional[str] = None) -> None:
292+
if not report_link_file_name:
293+
return
294+
report_link = getattr(diff_report, "report_url", "") or getattr(diff_report, "diff_url", "")
295+
if not report_link:
296+
return
297+
self.write_text_file(report_link_file_name, report_link + "\n")
298+
299+
def write_json_file(self, file_name: str, content: Any) -> None:
300+
file_path = Path(file_name)
301+
file_path.parent.mkdir(parents=True, exist_ok=True)
302+
with open(file_path, "w") as f:
303+
json.dump(content, f, indent=2)
304+
305+
def write_text_file(self, file_name: str, content: str) -> None:
306+
file_path = Path(file_name)
307+
file_path.parent.mkdir(parents=True, exist_ok=True)
308+
with open(file_path, "w") as f:
309+
f.write(content)
257310

258311
def output_gitlab_security(self, diff_report: Diff) -> None:
259312
"""

0 commit comments

Comments
 (0)