socketsecurity 2.2.80__tar.gz → 2.2.81__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 (123) hide show
  1. socketsecurity-2.2.81/.github/workflows/e2e-test.yml +99 -0
  2. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/PKG-INFO +1 -1
  3. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/docs/cli-reference.md +32 -3
  4. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/pyproject.toml +1 -1
  5. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/__init__.py +65 -2
  7. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/messages.py +41 -7
  8. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/socketcli.py +11 -2
  9. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/e2e/fixtures/simple-npm/package.json +2 -2
  10. socketsecurity-2.2.81/tests/e2e/fixtures/simple-pypi/requirements.txt +3 -0
  11. socketsecurity-2.2.81/tests/e2e/validate-gitlab.sh +63 -0
  12. socketsecurity-2.2.81/tests/e2e/validate-json.sh +33 -0
  13. socketsecurity-2.2.81/tests/e2e/validate-reachability.sh +65 -0
  14. socketsecurity-2.2.81/tests/e2e/validate-sarif.sh +19 -0
  15. socketsecurity-2.2.81/tests/e2e/validate-scan.sh +16 -0
  16. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_gitlab_format.py +175 -2
  17. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/uv.lock +1 -1
  18. socketsecurity-2.2.80/.github/workflows/e2e-test.yml +0 -201
  19. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/CODEOWNERS +0 -0
  20. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  21. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  22. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  23. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/workflows/docker-stable.yml +0 -0
  25. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/workflows/pr-preview.yml +0 -0
  26. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/workflows/python-tests.yml +0 -0
  27. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/workflows/release.yml +0 -0
  28. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/workflows/version-check.yml +0 -0
  29. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.github/zizmor.yml +0 -0
  30. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.gitignore +0 -0
  31. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.hooks/sync_version.py +0 -0
  32. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.pre-commit-config.yaml +0 -0
  33. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/.python-version +0 -0
  34. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/CHANGELOG.md +0 -0
  35. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/Dockerfile +0 -0
  36. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/LICENSE +0 -0
  37. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/Makefile +0 -0
  38. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/README.md +0 -0
  39. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/docs/ci-cd.md +0 -0
  40. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/docs/development.md +0 -0
  41. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/docs/troubleshooting.md +0 -0
  42. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-dashboard-parity.json +0 -0
  43. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-dashboard-parity.toml +0 -0
  44. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-diff-ci-cd.json +0 -0
  45. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-diff-ci-cd.toml +0 -0
  46. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-instance-detail.json +0 -0
  47. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/examples/config/sarif-instance-detail.toml +0 -0
  48. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/instructions/gitlab-commit-status/uat.md +0 -0
  49. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/pytest.ini +0 -0
  50. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/build_container.sh +0 -0
  51. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/build_container_flexible.sh +0 -0
  52. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/deploy-test-docker.sh +0 -0
  53. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/deploy-test-pypi.sh +0 -0
  54. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/docker-entrypoint.sh +0 -0
  55. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/scripts/run.sh +0 -0
  56. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/session.md +0 -0
  57. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socket.yml +0 -0
  58. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/config.py +0 -0
  59. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/alert_selection.py +0 -0
  60. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/classes.py +0 -0
  61. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/cli_client.py +0 -0
  62. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/exceptions.py +0 -0
  63. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/git_interface.py +0 -0
  64. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/helper/__init__.py +0 -0
  65. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  66. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/lazy_file_loader.py +0 -0
  67. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/logging.py +0 -0
  68. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/resource_utils.py +0 -0
  69. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm/__init__.py +0 -0
  70. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm/base.py +0 -0
  71. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm/client.py +0 -0
  72. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm/github.py +0 -0
  73. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm/gitlab.py +0 -0
  74. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/scm_comments.py +0 -0
  75. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/socket_config.py +0 -0
  76. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/tools/reachability.py +0 -0
  77. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/core/utils.py +0 -0
  78. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/output.py +0 -0
  79. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/__init__.py +0 -0
  80. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/base.py +0 -0
  81. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/formatters/__init__.py +0 -0
  82. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/formatters/slack.py +0 -0
  83. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/jira.py +0 -0
  84. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/manager.py +0 -0
  85. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/slack.py +0 -0
  86. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/teams.py +0 -0
  87. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/socketsecurity/plugins/webhook.py +0 -0
  88. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/__init__.py +0 -0
  89. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/conftest.py +0 -0
  90. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/create_diff_input.json +0 -0
  91. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_diff_alerts.py +0 -0
  92. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_diff_generation.py +0 -0
  93. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_has_manifest_files.py +0 -0
  94. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_package_and_alerts.py +0 -0
  95. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_sdk_methods.py +0 -0
  96. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/core/test_supporting_methods.py +0 -0
  97. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/create_response.json +0 -0
  98. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/diff/stream_diff.json +0 -0
  99. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  100. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/head_scan/metadata.json +0 -0
  101. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  102. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  103. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/new_scan/metadata.json +0 -0
  104. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  105. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/repos/repo_info_error.json +0 -0
  106. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/repos/repo_info_no_head.json +0 -0
  107. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/repos/repo_info_success.json +0 -0
  108. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/data/settings/security-policy.json +0 -0
  109. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  110. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/__init__.py +0 -0
  111. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_alert_selection.py +0 -0
  112. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_cli_config.py +0 -0
  113. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_client.py +0 -0
  114. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_config.py +0 -0
  115. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_gitlab_auth.py +0 -0
  116. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  117. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_gitlab_commit_status.py +0 -0
  118. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_output.py +0 -0
  119. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/tests/unit/test_slack_plugin.py +0 -0
  120. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/workflows/bitbucket-pipelines.yml +0 -0
  121. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/workflows/buildkite.yml +0 -0
  122. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/workflows/github-actions.yml +0 -0
  123. {socketsecurity-2.2.80 → socketsecurity-2.2.81}/workflows/gitlab-ci.yml +0 -0
@@ -0,0 +1,99 @@
1
+ name: E2E Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ e2e:
14
+ if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ include:
20
+ - name: scan
21
+ args: >-
22
+ --target-path tests/e2e/fixtures/simple-npm
23
+ --disable-blocking
24
+ --enable-debug
25
+ validate: tests/e2e/validate-scan.sh
26
+
27
+ - name: sarif
28
+ args: >-
29
+ --target-path tests/e2e/fixtures/simple-npm
30
+ --sarif-file /tmp/results.sarif
31
+ --disable-blocking
32
+ validate: tests/e2e/validate-sarif.sh
33
+
34
+ - name: reachability
35
+ args: >-
36
+ --target-path tests/e2e/fixtures/simple-npm
37
+ --reach
38
+ --disable-blocking
39
+ --enable-debug
40
+ validate: tests/e2e/validate-reachability.sh
41
+ setup-node: "true"
42
+
43
+ - name: gitlab
44
+ args: >-
45
+ --target-path tests/e2e/fixtures/simple-npm
46
+ --enable-gitlab-security
47
+ --disable-blocking
48
+ validate: tests/e2e/validate-gitlab.sh
49
+
50
+ - name: json
51
+ args: >-
52
+ --target-path tests/e2e/fixtures/simple-npm
53
+ --enable-json
54
+ --disable-blocking
55
+ validate: tests/e2e/validate-json.sh
56
+
57
+ - name: pypi
58
+ args: >-
59
+ --target-path tests/e2e/fixtures/simple-pypi
60
+ --disable-blocking
61
+ --enable-debug
62
+ validate: tests/e2e/validate-scan.sh
63
+
64
+ name: e2e-${{ matrix.name }}
65
+ steps:
66
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871
67
+ with:
68
+ fetch-depth: 0
69
+ persist-credentials: false
70
+
71
+ - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3
72
+ with:
73
+ python-version: '3.12'
74
+
75
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af
76
+ if: matrix.setup-node == 'true'
77
+ with:
78
+ node-version: '20'
79
+
80
+ - name: Install CLI from local repo
81
+ run: |
82
+ python -m pip install --upgrade pip
83
+ pip install .
84
+
85
+ - name: Install uv
86
+ if: matrix.setup-node == 'true'
87
+ run: pip install uv
88
+
89
+ - name: Run Socket CLI
90
+ env:
91
+ SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
92
+ run: |
93
+ set -o pipefail
94
+ socketcli ${{ matrix.args }} 2>&1 | tee /tmp/e2e-output.log
95
+
96
+ - name: Validate results
97
+ env:
98
+ SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
99
+ run: bash ${{ matrix.validate }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.2.80
3
+ Version: 2.2.81
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Project-URL: Homepage, https://socket.dev
6
6
  Author-email: Douglas Coburn <douglas@socket.dev>
@@ -700,17 +700,44 @@ The GitLab report includes **actionable security alerts** based on your Socket p
700
700
 
701
701
  All alert types are included in the GitLab report if they're marked as `error` or `warn` by your Socket Security policy, ensuring the Security Dashboard shows only actionable findings.
702
702
 
703
+ ### Alert Population: GitLab vs JSON/SARIF
704
+
705
+ The GitLab Security Dashboard report and the JSON/SARIF diff outputs use different alert selection strategies, reflecting their distinct purposes:
706
+
707
+ | Output Format | Default Alerts | With `--strict-blocking` |
708
+ |:---|:---|:---|
709
+ | `--enable-gitlab-security` | **All** alerts (new + existing) | All alerts (same) |
710
+ | `--enable-json` | New alerts only | New + existing alerts |
711
+ | `--enable-sarif` (diff scope) | New alerts only | New + existing alerts |
712
+
713
+ **Why the difference?** GitLab's Security Dashboard is designed to present the full security posture of a project. An empty dashboard on a scan with no dependency changes would be misleading -- the vulnerabilities still exist, they just didn't change. By contrast, JSON and SARIF in diff scope are designed to answer "what changed?" and only include existing alerts when `--strict-blocking` explicitly requests it.
714
+
715
+ > **Tip:** If you use `--enable-json` alongside `--enable-gitlab-security`, the GitLab report may contain more vulnerabilities than the JSON output. This is expected. To make JSON output match, add `--strict-blocking`.
716
+
717
+ ### Alert Ignoring via PR/MR Comments
718
+
719
+ When using the CLI with SCM integration (`--scm github` or `--scm gitlab`), users can ignore specific alerts by reacting to Socket's PR/MR comments. Ignored alerts are removed from `--enable-json`, `--enable-sarif`, and console output.
720
+
721
+ However, the GitLab Security Dashboard report includes **all** alerts matching your security policy (new and existing), regardless of comment-based ignores. This ensures the Security Dashboard always reflects the full set of known issues. To suppress a vulnerability from the GitLab report, adjust the alert's policy in Socket's dashboard rather than ignoring it via a PR comment.
722
+
703
723
  ### Report Schema
704
724
 
705
- Socket CLI generates reports compliant with [GitLab Dependency Scanning schema version 15.0.0](https://docs.gitlab.com/ee/development/integrations/secure.html). The reports include:
725
+ Socket CLI generates reports compliant with [GitLab Dependency Scanning schema version 15.0.0](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/v15.0.0/dist/dependency-scanning-report-format.json). The reports include:
706
726
 
707
- - **Scan metadata**: Analyzer and scanner information
727
+ - **Scan metadata**: Analyzer and scanner information with ISO 8601 timestamps
708
728
  - **Vulnerabilities**: Detailed vulnerability data with:
709
729
  - Unique deterministic UUIDs for tracking
710
730
  - Package location and dependency information
711
731
  - Severity levels mapped from Socket's analysis
712
732
  - Socket-specific alert types and CVE identifiers
713
733
  - Links to Socket.dev for detailed analysis
734
+ - **Dependency files**: Manifest files and their dependencies discovered during the scan
735
+
736
+ **Schema compatibility:** The v15.0.0 schema is supported across all GitLab versions 12.0+ (both self-hosted and cloud). The report includes the `dependency_files` field, which is required by v15.0.0 and accepted as an optional extra by newer schema versions, ensuring maximum compatibility across GitLab instances.
737
+
738
+ ### Performance Notes
739
+
740
+ When `--enable-gitlab-security` (or `--enable-json` / `--enable-sarif`) is used with a full scan (non-diff mode), the CLI fetches package and alert data from the scan results to populate the report. This adds time proportional to the number of packages in the scan. Without these output flags, no additional data is fetched and scan performance is unchanged.
714
741
 
715
742
  ### Requirements
716
743
 
@@ -726,7 +753,9 @@ Socket CLI generates reports compliant with [GitLab Dependency Scanning schema v
726
753
  - Ensure the report file follows the correct schema format
727
754
 
728
755
  **Empty vulnerabilities array:**
729
- - This is normal if no new security issues were detected
756
+ - The GitLab report includes both new and existing alerts, so repeated scans of the same repo should still populate the report as long as Socket detects actionable issues
757
+ - If the report is empty, verify the Socket dashboard shows alerts for the scanned packages -- an empty report means no error/warn-level alerts exist
758
+ - For full scans (non-diff mode), ensure you are using `--enable-gitlab-security` so alert data is fetched
730
759
  - Check Socket.dev dashboard for full analysis details
731
760
 
732
761
  ## Development
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.2.80"
9
+ version = "2.2.81"
10
10
  requires-python = ">= 3.11"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.2.80'
2
+ __version__ = '2.2.81'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -659,9 +659,48 @@ class Core:
659
659
  diff.report_url = f"{base_socket}/{self.config.org_slug}/sbom/{new_full_scan.id}"
660
660
  diff.diff_url = diff.report_url
661
661
  diff.id = new_full_scan.id
662
- diff.packages = {}
663
662
 
664
- # Return result in the format expected by the user
663
+ needs_alerts = (
664
+ self.cli_config is not None
665
+ and (
666
+ self.cli_config.enable_gitlab_security
667
+ or self.cli_config.enable_json
668
+ or self.cli_config.enable_sarif
669
+ )
670
+ )
671
+
672
+ if needs_alerts:
673
+ log.info("Output format requires alerts, fetching SBOM data for full scan")
674
+ sbom_start = time.time()
675
+ sbom_artifacts_dict = self.get_sbom_data(new_full_scan.id)
676
+ sbom_artifacts = self.get_sbom_data_list(sbom_artifacts_dict)
677
+ packages = self._create_packages_dict_without_license_text(sbom_artifacts)
678
+ diff.packages = packages
679
+
680
+ all_alerts_collection: Dict[str, List[Issue]] = {}
681
+ for package_id, package in packages.items():
682
+ self.add_package_alerts_to_collection(
683
+ package=package,
684
+ alerts_collection=all_alerts_collection,
685
+ packages=packages
686
+ )
687
+
688
+ consolidated: Set[str] = set()
689
+ for alert_key, alerts in all_alerts_collection.items():
690
+ for alert in alerts:
691
+ alert_str = f"{alert.purl},{alert.type}"
692
+ if (alert.error or alert.warn) and alert_str not in consolidated:
693
+ diff.new_alerts.append(alert)
694
+ consolidated.add(alert_str)
695
+
696
+ sbom_end = time.time()
697
+ log.info(
698
+ f"Fetched {len(packages)} packages and {len(diff.new_alerts)} alerts "
699
+ f"in {sbom_end - sbom_start:.2f}s"
700
+ )
701
+ else:
702
+ diff.packages = {}
703
+
665
704
  return diff
666
705
 
667
706
  def get_full_scan(self, full_scan_id: str) -> FullScan:
@@ -712,6 +751,30 @@ class Core:
712
751
 
713
752
  return packages
714
753
 
754
+ @staticmethod
755
+ def _create_packages_dict_without_license_text(
756
+ sbom_artifacts: list[SocketArtifact],
757
+ ) -> dict[str, Package]:
758
+ """Like create_packages_dict but skips the license-metadata API call.
759
+
760
+ Used when we only need packages for alert extraction (e.g. populating
761
+ GitLab/JSON/SARIF reports from a full scan) and don't need license text.
762
+ """
763
+ packages: dict[str, Package] = {}
764
+ top_level_count: dict[str, int] = {}
765
+ for artifact in sbom_artifacts:
766
+ package = Package.from_socket_artifact(asdict(artifact))
767
+ if package.id not in packages:
768
+ packages[package.id] = package
769
+ if package.topLevelAncestors:
770
+ for top_id in package.topLevelAncestors:
771
+ top_level_count[top_id] = top_level_count.get(top_id, 0) + 1
772
+
773
+ for package_id, package in packages.items():
774
+ package.transitives = top_level_count.get(package_id, 0)
775
+
776
+ return packages
777
+
715
778
  def get_package_license_text(self, package: Package) -> str:
716
779
  """
717
780
  Gets the license text for a package if available.
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  import re
5
5
  import uuid
6
- from datetime import datetime
6
+ from datetime import datetime, timezone
7
7
  from pathlib import Path
8
8
  from mdutils import MdUtils
9
9
  from prettytable import PrettyTable
@@ -593,6 +593,20 @@ class Messages:
593
593
  output["new_alerts"].append(json.loads(str(alert)))
594
594
  return output
595
595
 
596
+ @staticmethod
597
+ def _pkg_type_to_package_manager(pkg_type: str) -> str:
598
+ """Map Socket pkg_type to GitLab package_manager name for dependency_files."""
599
+ mapping = {
600
+ "npm": "npm",
601
+ "pypi": "pip",
602
+ "go": "go",
603
+ "maven": "maven",
604
+ "gem": "bundler",
605
+ "nuget": "nuget",
606
+ "cargo": "cargo",
607
+ }
608
+ return mapping.get(pkg_type, pkg_type or "unknown")
609
+
596
610
  @staticmethod
597
611
  def map_socket_severity_to_gitlab(severity: str) -> str:
598
612
  """
@@ -743,15 +757,18 @@ class Messages:
743
757
  }
744
758
  },
745
759
  "type": "dependency_scanning",
746
- "start_time": datetime.utcnow().isoformat() + "Z",
747
- "end_time": datetime.utcnow().isoformat() + "Z",
760
+ "start_time": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S"),
761
+ "end_time": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S"),
748
762
  "status": "success"
749
763
  },
750
- "vulnerabilities": []
764
+ "vulnerabilities": [],
765
+ "dependency_files": []
751
766
  }
752
767
 
753
- # Process each alert
754
- for alert in diff.new_alerts:
768
+ dep_files_map: dict = {}
769
+
770
+ all_alerts = list(diff.new_alerts) + list(getattr(diff, 'unchanged_alerts', []))
771
+ for alert in all_alerts:
755
772
  vulnerability = {
756
773
  "id": Messages.generate_uuid_from_alert_gitlab(alert),
757
774
  "category": "dependency_scanning",
@@ -764,12 +781,29 @@ class Messages:
764
781
  "location": Messages.extract_location_gitlab(alert)
765
782
  }
766
783
 
767
- # Add solution if available
768
784
  if hasattr(alert, 'suggestion') and alert.suggestion:
769
785
  vulnerability["solution"] = alert.suggestion
770
786
 
771
787
  gitlab_report["vulnerabilities"].append(vulnerability)
772
788
 
789
+ file_path = vulnerability["location"]["file"]
790
+ if file_path != "unknown":
791
+ pkg_manager = Messages._pkg_type_to_package_manager(
792
+ alert.pkg_type if hasattr(alert, 'pkg_type') else ""
793
+ )
794
+ if file_path not in dep_files_map:
795
+ dep_files_map[file_path] = {
796
+ "path": file_path,
797
+ "package_manager": pkg_manager,
798
+ "dependencies": []
799
+ }
800
+ dep_files_map[file_path]["dependencies"].append({
801
+ "package": {"name": alert.pkg_name},
802
+ "version": alert.pkg_version
803
+ })
804
+
805
+ gitlab_report["dependency_files"] = list(dep_files_map.values())
806
+
773
807
  return gitlab_report
774
808
 
775
809
  @staticmethod
@@ -649,11 +649,20 @@ def main_code():
649
649
  scm.enable_merge_pipeline_check()
650
650
  passed = output_handler.report_pass(diff)
651
651
  state = "success" if passed else "failed"
652
- blocking_count = sum(1 for a in diff.new_alerts if a.error)
652
+ new_blocking = sum(1 for a in diff.new_alerts if a.error)
653
+ unchanged_blocking = 0
654
+ if config.strict_blocking and hasattr(diff, 'unchanged_alerts'):
655
+ unchanged_blocking = sum(1 for a in diff.unchanged_alerts if a.error)
656
+ blocking_count = new_blocking + unchanged_blocking
653
657
  if passed:
654
658
  description = "No blocking issues"
655
659
  else:
656
- description = f"{blocking_count} blocking alert(s) found"
660
+ parts = []
661
+ if new_blocking:
662
+ parts.append(f"{new_blocking} new")
663
+ if unchanged_blocking:
664
+ parts.append(f"{unchanged_blocking} existing")
665
+ description = f"{blocking_count} blocking alert(s) found ({', '.join(parts)})"
657
666
  target_url = diff.report_url or diff.diff_url or ""
658
667
  scm.set_commit_status(state, description, target_url)
659
668
 
@@ -4,9 +4,9 @@
4
4
  "description": "Test fixture for reachability analysis",
5
5
  "main": "index.js",
6
6
  "dependencies": {
7
- "lodash": "4.17.23",
7
+ "lodash": "4.18.1",
8
8
  "express": "4.22.0",
9
- "axios": "1.13.5"
9
+ "axios": "1.15.0"
10
10
  },
11
11
  "devDependencies": {
12
12
  "typescript": "5.0.4",
@@ -0,0 +1,3 @@
1
+ requests==2.31.0
2
+ flask==3.0.0
3
+ pyyaml==6.0.1
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPORT="gl-dependency-scanning-report.json"
5
+
6
+ if [ ! -f "$REPORT" ]; then
7
+ echo "FAIL: GitLab report not found at $REPORT"
8
+ exit 1
9
+ fi
10
+
11
+ python3 -c "
12
+ import json, re, sys
13
+
14
+ with open('$REPORT') as f:
15
+ data = json.load(f)
16
+
17
+ errors = []
18
+
19
+ # v15.0.0 required root-level keys
20
+ for key in ('version', 'scan', 'vulnerabilities', 'dependency_files'):
21
+ if key not in data:
22
+ errors.append(f'Missing required root key: {key}')
23
+
24
+ if 'scan' in data:
25
+ scan = data['scan']
26
+
27
+ # Timestamp format: YYYY-MM-DDTHH:MM:SS (no microseconds, no trailing Z)
28
+ ts_pattern = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$')
29
+ for field in ('start_time', 'end_time'):
30
+ val = scan.get(field, '')
31
+ if not ts_pattern.match(val):
32
+ errors.append(f'scan.{field} \"{val}\" does not match pattern YYYY-MM-DDTHH:MM:SS')
33
+
34
+ if scan.get('type') != 'dependency_scanning':
35
+ errors.append(f'scan.type is \"{scan.get(\"type\")}\" expected \"dependency_scanning\"')
36
+
37
+ analyzer_id = scan.get('analyzer', {}).get('id', '')
38
+ if analyzer_id != 'socket-security':
39
+ errors.append(f'scan.analyzer.id is \"{analyzer_id}\" expected \"socket-security\"')
40
+
41
+ scanner_id = scan.get('scanner', {}).get('id', '')
42
+ if scanner_id != 'socket-cli':
43
+ errors.append(f'scan.scanner.id is \"{scanner_id}\" expected \"socket-cli\"')
44
+
45
+ if scan.get('status') != 'success':
46
+ errors.append(f'scan.status is \"{scan.get(\"status\")}\" expected \"success\"')
47
+
48
+ # dependency_files structure check
49
+ if 'dependency_files' in data:
50
+ for i, df in enumerate(data['dependency_files']):
51
+ for req in ('path', 'package_manager', 'dependencies'):
52
+ if req not in df:
53
+ errors.append(f'dependency_files[{i}] missing required key: {req}')
54
+
55
+ if errors:
56
+ for e in errors:
57
+ print(f'FAIL: {e}')
58
+ sys.exit(1)
59
+
60
+ vuln_count = len(data.get('vulnerabilities', []))
61
+ dep_file_count = len(data.get('dependency_files', []))
62
+ print(f'PASS: Valid GitLab v15.0.0 report with {vuln_count} vulnerability(ies) and {dep_file_count} dependency file(s)')
63
+ "
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LOG="/tmp/e2e-output.log"
5
+
6
+ python3 -c "
7
+ import json, sys
8
+
9
+ # The JSON output may be prefixed with a logger timestamp (e.g. '2026-04-08 22:46:50,580: {...}').
10
+ # Try parsing the full line first, then from the first '{' character.
11
+ found = False
12
+ with open('$LOG') as f:
13
+ for line in f:
14
+ line = line.strip()
15
+ if not line or '{' not in line:
16
+ continue
17
+ # Try full line first, then from the first brace
18
+ for candidate in (line, line[line.index('{'):]):
19
+ try:
20
+ data = json.loads(candidate)
21
+ if isinstance(data, dict):
22
+ found = True
23
+ print(f'PASS: Valid JSON output with {len(data)} top-level key(s)')
24
+ break
25
+ except json.JSONDecodeError:
26
+ continue
27
+ if found:
28
+ break
29
+
30
+ if not found:
31
+ print('FAIL: No valid JSON object found in output')
32
+ sys.exit(1)
33
+ "
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LOG="/tmp/e2e-output.log"
5
+
6
+ # 1. Verify reachability analysis completed
7
+ if grep -q "Reachability analysis completed successfully" "$LOG"; then
8
+ echo "PASS: Reachability analysis completed"
9
+ grep "Reachability analysis completed successfully" "$LOG"
10
+ grep "Results written to:" "$LOG" || true
11
+ else
12
+ echo "FAIL: Reachability analysis did not complete successfully"
13
+ cat "$LOG"
14
+ exit 1
15
+ fi
16
+
17
+ # 2. Verify scan produced a report URL
18
+ if grep -q "Full scan report URL: https://socket.dev/" "$LOG"; then
19
+ echo "PASS: Full scan report URL found"
20
+ grep "Full scan report URL:" "$LOG"
21
+ elif grep -q "Diff Url: https://socket.dev/" "$LOG"; then
22
+ echo "PASS: Diff URL found"
23
+ grep "Diff Url:" "$LOG"
24
+ else
25
+ echo "FAIL: No report URL found in scan output"
26
+ cat "$LOG"
27
+ exit 1
28
+ fi
29
+
30
+ # 3. Run SARIF with --sarif-reachability all
31
+ socketcli \
32
+ --target-path tests/e2e/fixtures/simple-npm \
33
+ --reach \
34
+ --sarif-file /tmp/sarif-all.sarif \
35
+ --sarif-scope full \
36
+ --sarif-reachability all \
37
+ --disable-blocking \
38
+ 2>/dev/null
39
+
40
+ # 4. Run SARIF with --sarif-reachability reachable (filtered)
41
+ socketcli \
42
+ --target-path tests/e2e/fixtures/simple-npm \
43
+ --reach \
44
+ --sarif-file /tmp/sarif-reachable.sarif \
45
+ --sarif-scope full \
46
+ --sarif-reachability reachable \
47
+ --disable-blocking \
48
+ 2>/dev/null
49
+
50
+ # 5. Verify reachable-only results are a subset of all results
51
+ test -f /tmp/sarif-all.sarif
52
+ test -f /tmp/sarif-reachable.sarif
53
+
54
+ python3 -c "
55
+ import json
56
+ with open('/tmp/sarif-all.sarif') as f:
57
+ all_data = json.load(f)
58
+ with open('/tmp/sarif-reachable.sarif') as f:
59
+ reach_data = json.load(f)
60
+ all_count = len(all_data['runs'][0]['results'])
61
+ reach_count = len(reach_data['runs'][0]['results'])
62
+ print(f'All results: {all_count}, Reachable-only results: {reach_count}')
63
+ assert reach_count <= all_count, f'FAIL: reachable ({reach_count}) > all ({all_count})'
64
+ print('PASS: Reachable-only results is a subset of all results')
65
+ "
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SARIF="/tmp/results.sarif"
5
+
6
+ if [ ! -f "$SARIF" ]; then
7
+ echo "FAIL: SARIF file not found at $SARIF"
8
+ exit 1
9
+ fi
10
+
11
+ python3 -c "
12
+ import json, sys
13
+ with open('$SARIF') as f:
14
+ data = json.load(f)
15
+ assert data['version'] == '2.1.0', f'Invalid version: {data[\"version\"]}'
16
+ assert '\$schema' in data, 'Missing \$schema'
17
+ count = len(data['runs'][0]['results'])
18
+ print(f'PASS: Valid SARIF 2.1.0 with {count} result(s)')
19
+ "
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ LOG="/tmp/e2e-output.log"
5
+
6
+ if grep -q "Full scan report URL: https://socket.dev/" "$LOG"; then
7
+ echo "PASS: Full scan report URL found"
8
+ grep "Full scan report URL:" "$LOG"
9
+ elif grep -q "Diff Url: https://socket.dev/" "$LOG"; then
10
+ echo "PASS: Diff URL found"
11
+ grep "Diff Url:" "$LOG"
12
+ else
13
+ echo "FAIL: No report URL found in scan output"
14
+ cat "$LOG"
15
+ exit 1
16
+ fi