socketsecurity 2.1.35__tar.gz → 2.2.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.
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/workflows/docker-stable.yml +8 -7
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/workflows/pr-preview.yml +7 -7
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/workflows/release.yml +6 -6
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/PKG-INFO +79 -2
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/README.md +77 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/pyproject.toml +2 -2
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/requirements.txt +1 -1
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/config.py +7 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/__init__.py +72 -43
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/git_interface.py +61 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/messages.py +58 -4
- socketsecurity-2.2.2/socketsecurity/core/scm/client.py +84 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/scm/gitlab.py +112 -8
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/socketcli.py +28 -1
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/unit/test_cli_config.py +14 -2
- socketsecurity-2.2.2/tests/unit/test_gitlab_auth.py +116 -0
- socketsecurity-2.2.2/tests/unit/test_gitlab_auth_fallback.py +148 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/workflows/gitlab-ci.yml +3 -0
- socketsecurity-2.1.35/socketsecurity/core/scm/client.py +0 -41
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.gitignore +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/.python-version +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/Dockerfile +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/LICENSE +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/Makefile +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/Pipfile.lock +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/docs/README.md +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/pytest.ini +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/requirements-dev.lock +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/requirements-dev.txt +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/requirements.lock +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/scripts/build_container.sh +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/scripts/run.sh +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/output.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/__init__.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/conftest.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/test_sdk_methods.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.1.35 → socketsecurity-2.2.2}/workflows/github-actions.yml +0 -0
|
@@ -21,18 +21,18 @@ jobs:
|
|
|
21
21
|
fi
|
|
22
22
|
echo "Version ${{ inputs.version }} found on PyPI - proceeding with release"
|
|
23
23
|
|
|
24
|
-
- name: Login to Docker Hub
|
|
25
|
-
uses: docker/login-action@v3
|
|
26
|
-
with:
|
|
27
|
-
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
28
|
-
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
29
|
-
|
|
30
24
|
- name: Set up QEMU
|
|
31
25
|
uses: docker/setup-qemu-action@v3
|
|
32
26
|
|
|
33
27
|
- name: Set up Docker Buildx
|
|
34
28
|
uses: docker/setup-buildx-action@v3
|
|
35
29
|
|
|
30
|
+
- name: Login to Docker Hub with Organization Token
|
|
31
|
+
uses: docker/login-action@v3
|
|
32
|
+
with:
|
|
33
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
34
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
35
|
+
|
|
36
36
|
- name: Build & Push Stable Docker
|
|
37
37
|
uses: docker/build-push-action@v5
|
|
38
38
|
with:
|
|
@@ -40,4 +40,5 @@ jobs:
|
|
|
40
40
|
platforms: linux/amd64,linux/arm64
|
|
41
41
|
tags: socketdev/cli:stable
|
|
42
42
|
build-args: |
|
|
43
|
-
CLI_VERSION=${{ inputs.version }}
|
|
43
|
+
CLI_VERSION=${{ inputs.version }}
|
|
44
|
+
|
|
@@ -119,19 +119,19 @@ jobs:
|
|
|
119
119
|
echo "success=false" >> $GITHUB_OUTPUT
|
|
120
120
|
exit 1
|
|
121
121
|
|
|
122
|
-
- name: Login to Docker Hub
|
|
123
|
-
if: steps.verify_package.outputs.success == 'true'
|
|
124
|
-
uses: docker/login-action@v3
|
|
125
|
-
with:
|
|
126
|
-
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
127
|
-
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
128
|
-
|
|
129
122
|
- name: Set up QEMU
|
|
130
123
|
uses: docker/setup-qemu-action@v3
|
|
131
124
|
|
|
132
125
|
- name: Set up Docker Buildx
|
|
133
126
|
uses: docker/setup-buildx-action@v3
|
|
134
127
|
|
|
128
|
+
- name: Login to Docker Hub with Organization Token
|
|
129
|
+
if: steps.verify_package.outputs.success == 'true'
|
|
130
|
+
uses: docker/login-action@v3
|
|
131
|
+
with:
|
|
132
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
133
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
134
|
+
|
|
135
135
|
- name: Build & Push Docker Preview
|
|
136
136
|
if: steps.verify_package.outputs.success == 'true'
|
|
137
137
|
uses: docker/build-push-action@v5
|
|
@@ -68,18 +68,18 @@ jobs:
|
|
|
68
68
|
if: steps.version_check.outputs.pypi_exists != 'true'
|
|
69
69
|
uses: pypa/gh-action-pypi-publish@v1.12.4
|
|
70
70
|
|
|
71
|
-
- name: Login to Docker Hub
|
|
72
|
-
uses: docker/login-action@v3
|
|
73
|
-
with:
|
|
74
|
-
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
75
|
-
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
76
|
-
|
|
77
71
|
- name: Set up QEMU
|
|
78
72
|
uses: docker/setup-qemu-action@v3
|
|
79
73
|
|
|
80
74
|
- name: Set up Docker Buildx
|
|
81
75
|
uses: docker/setup-buildx-action@v3
|
|
82
76
|
|
|
77
|
+
- name: Login to Docker Hub with Organization Token
|
|
78
|
+
uses: docker/login-action@v3
|
|
79
|
+
with:
|
|
80
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
81
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
82
|
+
|
|
83
83
|
- name: Verify package is installable
|
|
84
84
|
id: verify_package
|
|
85
85
|
env:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: socketsecurity
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.2
|
|
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>
|
|
@@ -39,7 +39,7 @@ Requires-Dist: packaging
|
|
|
39
39
|
Requires-Dist: prettytable
|
|
40
40
|
Requires-Dist: python-dotenv
|
|
41
41
|
Requires-Dist: requests
|
|
42
|
-
Requires-Dist: socket-sdk-python<3,>=2.1.
|
|
42
|
+
Requires-Dist: socket-sdk-python<3,>=2.1.8
|
|
43
43
|
Provides-Extra: dev
|
|
44
44
|
Requires-Dist: hatch; extra == 'dev'
|
|
45
45
|
Requires-Dist: pip-tools>=7.4.0; extra == 'dev'
|
|
@@ -172,6 +172,7 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
172
172
|
|:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
|
|
173
173
|
| --ignore-commit-files | False | False | Ignore commit files |
|
|
174
174
|
| --disable-blocking | False | False | Disable blocking mode |
|
|
175
|
+
| --enable-diff | False | False | Enable diff mode even when using --integration api (forces diff mode without SCM integration) |
|
|
175
176
|
| --scm | False | api | Source control management type |
|
|
176
177
|
| --timeout | False | | Timeout in seconds for API requests |
|
|
177
178
|
| --include-module-folders | False | False | If enabled will include manifest files from folders like node_modules |
|
|
@@ -234,6 +235,74 @@ The CLI uses intelligent default branch detection with the following priority:
|
|
|
234
235
|
|
|
235
236
|
Both `--default-branch` and `--pending-head` parameters are automatically synchronized to ensure consistent behavior.
|
|
236
237
|
|
|
238
|
+
## GitLab Token Configuration
|
|
239
|
+
|
|
240
|
+
The CLI supports GitLab integration with automatic authentication pattern detection for different token types.
|
|
241
|
+
|
|
242
|
+
### Supported Token Types
|
|
243
|
+
|
|
244
|
+
GitLab API supports two authentication methods, and the CLI automatically detects which one to use:
|
|
245
|
+
|
|
246
|
+
1. **Bearer Token Authentication** (`Authorization: Bearer <token>`)
|
|
247
|
+
- GitLab CI Job Tokens (`$CI_JOB_TOKEN`)
|
|
248
|
+
- Personal Access Tokens with `glpat-` prefix
|
|
249
|
+
- OAuth 2.0 tokens (long alphanumeric tokens)
|
|
250
|
+
|
|
251
|
+
2. **Private Token Authentication** (`PRIVATE-TOKEN: <token>`)
|
|
252
|
+
- Legacy personal access tokens
|
|
253
|
+
- Custom tokens that don't match Bearer patterns
|
|
254
|
+
|
|
255
|
+
### Token Detection Logic
|
|
256
|
+
|
|
257
|
+
The CLI automatically determines the authentication method using this logic:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
if token == $CI_JOB_TOKEN:
|
|
261
|
+
use Bearer authentication
|
|
262
|
+
elif token starts with "glpat-":
|
|
263
|
+
use Bearer authentication
|
|
264
|
+
elif token is long (>40 chars) and alphanumeric:
|
|
265
|
+
use Bearer authentication
|
|
266
|
+
else:
|
|
267
|
+
use PRIVATE-TOKEN authentication
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Automatic Fallback
|
|
271
|
+
|
|
272
|
+
If the initial authentication method fails with a 401 error, the CLI automatically retries with the alternative method:
|
|
273
|
+
|
|
274
|
+
- **Bearer → PRIVATE-TOKEN**: If Bearer authentication fails, retry with PRIVATE-TOKEN
|
|
275
|
+
- **PRIVATE-TOKEN → Bearer**: If PRIVATE-TOKEN fails, retry with Bearer authentication
|
|
276
|
+
|
|
277
|
+
This ensures maximum compatibility across different GitLab configurations and token types.
|
|
278
|
+
|
|
279
|
+
### Environment Variables
|
|
280
|
+
|
|
281
|
+
| Variable | Description | Example |
|
|
282
|
+
|:---------|:------------|:--------|
|
|
283
|
+
| `GITLAB_TOKEN` | GitLab API token (required for GitLab integration) | `glpat-xxxxxxxxxxxxxxxxxxxx` |
|
|
284
|
+
| `CI_JOB_TOKEN` | GitLab CI job token (automatically used in GitLab CI) | Automatically provided by GitLab CI |
|
|
285
|
+
|
|
286
|
+
### Usage Examples
|
|
287
|
+
|
|
288
|
+
**GitLab CI with job token (recommended):**
|
|
289
|
+
```yaml
|
|
290
|
+
variables:
|
|
291
|
+
GITLAB_TOKEN: $CI_JOB_TOKEN
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**GitLab CI with personal access token:**
|
|
295
|
+
```yaml
|
|
296
|
+
variables:
|
|
297
|
+
GITLAB_TOKEN: $GITLAB_PERSONAL_ACCESS_TOKEN # Set in GitLab project/group variables
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Local development:**
|
|
301
|
+
```bash
|
|
302
|
+
export GITLAB_TOKEN="glpat-your-personal-access-token"
|
|
303
|
+
socketcli --integration gitlab --repo owner/repo --pr-number 123
|
|
304
|
+
```
|
|
305
|
+
|
|
237
306
|
### Scan Behavior
|
|
238
307
|
|
|
239
308
|
The CLI determines scanning behavior intelligently:
|
|
@@ -261,6 +330,7 @@ The CLI determines which files to scan based on the following logic:
|
|
|
261
330
|
- **Differential Mode**: When manifest files are detected in changes, performs a diff scan with PR/MR comment integration
|
|
262
331
|
- **API Mode**: When no manifest files are in changes, creates a full scan report without PR comments but still scans the entire repository
|
|
263
332
|
- **Force Mode**: With `--ignore-commit-files`, always performs a full scan regardless of changes
|
|
333
|
+
- **Forced Diff Mode**: With `--enable-diff`, forces differential mode even when using `--integration api` (without SCM integration)
|
|
264
334
|
|
|
265
335
|
### Examples
|
|
266
336
|
|
|
@@ -268,6 +338,7 @@ The CLI determines which files to scan based on the following logic:
|
|
|
268
338
|
- **Commit without manifest files**: If your commit only changes non-manifest files (like `.github/workflows/socket.yaml`), the CLI automatically switches to API mode and performs a full repository scan.
|
|
269
339
|
- **Using `--files`**: If you specify `--files '["package.json"]'`, the CLI will check if this file exists and is a manifest file before determining scan type.
|
|
270
340
|
- **Using `--ignore-commit-files`**: This forces a full scan of all manifest files in the target path, regardless of what's in your commit.
|
|
341
|
+
- **Using `--enable-diff`**: Forces diff mode without SCM integration - useful when you want differential scanning but are using `--integration api`. For example: `socketcli --integration api --enable-diff --target-path /path/to/repo`
|
|
271
342
|
- **Auto-detection**: Most CI/CD scenarios now work with just `socketcli --target-path /path/to/repo --scm github --pr-number $PR_NUM`
|
|
272
343
|
|
|
273
344
|
## Debugging and Troubleshooting
|
|
@@ -393,4 +464,10 @@ Implementation targets:
|
|
|
393
464
|
|
|
394
465
|
### Environment Variables
|
|
395
466
|
|
|
467
|
+
#### Core Configuration
|
|
468
|
+
- `SOCKET_SECURITY_API_KEY`: Socket Security API token (alternative to --api-token parameter)
|
|
396
469
|
- `SOCKET_SDK_PATH`: Path to local socket-sdk-python repository (default: ../socket-sdk-python)
|
|
470
|
+
|
|
471
|
+
#### GitLab Integration
|
|
472
|
+
- `GITLAB_TOKEN`: GitLab API token for GitLab integration (supports both Bearer and PRIVATE-TOKEN authentication)
|
|
473
|
+
- `CI_JOB_TOKEN`: GitLab CI job token (automatically provided in GitLab CI environments)
|
|
@@ -116,6 +116,7 @@ If you don't want to provide the Socket API Token every time then you can use th
|
|
|
116
116
|
|:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
|
|
117
117
|
| --ignore-commit-files | False | False | Ignore commit files |
|
|
118
118
|
| --disable-blocking | False | False | Disable blocking mode |
|
|
119
|
+
| --enable-diff | False | False | Enable diff mode even when using --integration api (forces diff mode without SCM integration) |
|
|
119
120
|
| --scm | False | api | Source control management type |
|
|
120
121
|
| --timeout | False | | Timeout in seconds for API requests |
|
|
121
122
|
| --include-module-folders | False | False | If enabled will include manifest files from folders like node_modules |
|
|
@@ -178,6 +179,74 @@ The CLI uses intelligent default branch detection with the following priority:
|
|
|
178
179
|
|
|
179
180
|
Both `--default-branch` and `--pending-head` parameters are automatically synchronized to ensure consistent behavior.
|
|
180
181
|
|
|
182
|
+
## GitLab Token Configuration
|
|
183
|
+
|
|
184
|
+
The CLI supports GitLab integration with automatic authentication pattern detection for different token types.
|
|
185
|
+
|
|
186
|
+
### Supported Token Types
|
|
187
|
+
|
|
188
|
+
GitLab API supports two authentication methods, and the CLI automatically detects which one to use:
|
|
189
|
+
|
|
190
|
+
1. **Bearer Token Authentication** (`Authorization: Bearer <token>`)
|
|
191
|
+
- GitLab CI Job Tokens (`$CI_JOB_TOKEN`)
|
|
192
|
+
- Personal Access Tokens with `glpat-` prefix
|
|
193
|
+
- OAuth 2.0 tokens (long alphanumeric tokens)
|
|
194
|
+
|
|
195
|
+
2. **Private Token Authentication** (`PRIVATE-TOKEN: <token>`)
|
|
196
|
+
- Legacy personal access tokens
|
|
197
|
+
- Custom tokens that don't match Bearer patterns
|
|
198
|
+
|
|
199
|
+
### Token Detection Logic
|
|
200
|
+
|
|
201
|
+
The CLI automatically determines the authentication method using this logic:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
if token == $CI_JOB_TOKEN:
|
|
205
|
+
use Bearer authentication
|
|
206
|
+
elif token starts with "glpat-":
|
|
207
|
+
use Bearer authentication
|
|
208
|
+
elif token is long (>40 chars) and alphanumeric:
|
|
209
|
+
use Bearer authentication
|
|
210
|
+
else:
|
|
211
|
+
use PRIVATE-TOKEN authentication
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Automatic Fallback
|
|
215
|
+
|
|
216
|
+
If the initial authentication method fails with a 401 error, the CLI automatically retries with the alternative method:
|
|
217
|
+
|
|
218
|
+
- **Bearer → PRIVATE-TOKEN**: If Bearer authentication fails, retry with PRIVATE-TOKEN
|
|
219
|
+
- **PRIVATE-TOKEN → Bearer**: If PRIVATE-TOKEN fails, retry with Bearer authentication
|
|
220
|
+
|
|
221
|
+
This ensures maximum compatibility across different GitLab configurations and token types.
|
|
222
|
+
|
|
223
|
+
### Environment Variables
|
|
224
|
+
|
|
225
|
+
| Variable | Description | Example |
|
|
226
|
+
|:---------|:------------|:--------|
|
|
227
|
+
| `GITLAB_TOKEN` | GitLab API token (required for GitLab integration) | `glpat-xxxxxxxxxxxxxxxxxxxx` |
|
|
228
|
+
| `CI_JOB_TOKEN` | GitLab CI job token (automatically used in GitLab CI) | Automatically provided by GitLab CI |
|
|
229
|
+
|
|
230
|
+
### Usage Examples
|
|
231
|
+
|
|
232
|
+
**GitLab CI with job token (recommended):**
|
|
233
|
+
```yaml
|
|
234
|
+
variables:
|
|
235
|
+
GITLAB_TOKEN: $CI_JOB_TOKEN
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**GitLab CI with personal access token:**
|
|
239
|
+
```yaml
|
|
240
|
+
variables:
|
|
241
|
+
GITLAB_TOKEN: $GITLAB_PERSONAL_ACCESS_TOKEN # Set in GitLab project/group variables
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Local development:**
|
|
245
|
+
```bash
|
|
246
|
+
export GITLAB_TOKEN="glpat-your-personal-access-token"
|
|
247
|
+
socketcli --integration gitlab --repo owner/repo --pr-number 123
|
|
248
|
+
```
|
|
249
|
+
|
|
181
250
|
### Scan Behavior
|
|
182
251
|
|
|
183
252
|
The CLI determines scanning behavior intelligently:
|
|
@@ -205,6 +274,7 @@ The CLI determines which files to scan based on the following logic:
|
|
|
205
274
|
- **Differential Mode**: When manifest files are detected in changes, performs a diff scan with PR/MR comment integration
|
|
206
275
|
- **API Mode**: When no manifest files are in changes, creates a full scan report without PR comments but still scans the entire repository
|
|
207
276
|
- **Force Mode**: With `--ignore-commit-files`, always performs a full scan regardless of changes
|
|
277
|
+
- **Forced Diff Mode**: With `--enable-diff`, forces differential mode even when using `--integration api` (without SCM integration)
|
|
208
278
|
|
|
209
279
|
### Examples
|
|
210
280
|
|
|
@@ -212,6 +282,7 @@ The CLI determines which files to scan based on the following logic:
|
|
|
212
282
|
- **Commit without manifest files**: If your commit only changes non-manifest files (like `.github/workflows/socket.yaml`), the CLI automatically switches to API mode and performs a full repository scan.
|
|
213
283
|
- **Using `--files`**: If you specify `--files '["package.json"]'`, the CLI will check if this file exists and is a manifest file before determining scan type.
|
|
214
284
|
- **Using `--ignore-commit-files`**: This forces a full scan of all manifest files in the target path, regardless of what's in your commit.
|
|
285
|
+
- **Using `--enable-diff`**: Forces diff mode without SCM integration - useful when you want differential scanning but are using `--integration api`. For example: `socketcli --integration api --enable-diff --target-path /path/to/repo`
|
|
215
286
|
- **Auto-detection**: Most CI/CD scenarios now work with just `socketcli --target-path /path/to/repo --scm github --pr-number $PR_NUM`
|
|
216
287
|
|
|
217
288
|
## Debugging and Troubleshooting
|
|
@@ -337,4 +408,10 @@ Implementation targets:
|
|
|
337
408
|
|
|
338
409
|
### Environment Variables
|
|
339
410
|
|
|
411
|
+
#### Core Configuration
|
|
412
|
+
- `SOCKET_SECURITY_API_KEY`: Socket Security API token (alternative to --api-token parameter)
|
|
340
413
|
- `SOCKET_SDK_PATH`: Path to local socket-sdk-python repository (default: ../socket-sdk-python)
|
|
414
|
+
|
|
415
|
+
#### GitLab Integration
|
|
416
|
+
- `GITLAB_TOKEN`: GitLab API token for GitLab integration (supports both Bearer and PRIVATE-TOKEN authentication)
|
|
417
|
+
- `CI_JOB_TOKEN`: GitLab CI job token (automatically provided in GitLab CI environments)
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "socketsecurity"
|
|
9
|
-
version = "2.
|
|
9
|
+
version = "2.2.2"
|
|
10
10
|
requires-python = ">= 3.10"
|
|
11
11
|
license = {"file" = "LICENSE"}
|
|
12
12
|
dependencies = [
|
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
|
16
16
|
'GitPython',
|
|
17
17
|
'packaging',
|
|
18
18
|
'python-dotenv',
|
|
19
|
-
'socket-sdk-python>=2.1.
|
|
19
|
+
'socket-sdk-python>=2.1.8,<3'
|
|
20
20
|
]
|
|
21
21
|
readme = "README.md"
|
|
22
22
|
description = "Socket Security CLI for CI/CD"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__author__ = 'socket.dev'
|
|
2
|
-
__version__ = '2.
|
|
2
|
+
__version__ = '2.2.2'
|
|
@@ -48,6 +48,7 @@ class CliConfig:
|
|
|
48
48
|
integration_type: IntegrationType = "api"
|
|
49
49
|
integration_org_slug: Optional[str] = None
|
|
50
50
|
pending_head: bool = False
|
|
51
|
+
enable_diff: bool = False
|
|
51
52
|
timeout: Optional[int] = 1200
|
|
52
53
|
exclude_license_details: bool = False
|
|
53
54
|
include_module_folders: bool = False
|
|
@@ -421,6 +422,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
421
422
|
action="store_true",
|
|
422
423
|
help=argparse.SUPPRESS
|
|
423
424
|
)
|
|
425
|
+
advanced_group.add_argument(
|
|
426
|
+
"--enable-diff",
|
|
427
|
+
dest="enable_diff",
|
|
428
|
+
action="store_true",
|
|
429
|
+
help="Enable diff mode even when using --integration api (forces diff mode without SCM integration)"
|
|
430
|
+
)
|
|
424
431
|
advanced_group.add_argument(
|
|
425
432
|
"--scm",
|
|
426
433
|
metavar="<type>",
|
|
@@ -2,6 +2,7 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
4
|
import tarfile
|
|
5
|
+
import tempfile
|
|
5
6
|
import time
|
|
6
7
|
import io
|
|
7
8
|
import json
|
|
@@ -30,7 +31,6 @@ from socketsecurity.core.exceptions import APIResourceNotFound
|
|
|
30
31
|
from .socket_config import SocketConfig
|
|
31
32
|
from .utils import socket_globs
|
|
32
33
|
from .resource_utils import check_file_count_against_ulimit
|
|
33
|
-
from .lazy_file_loader import load_files_for_sending_lazy
|
|
34
34
|
import importlib
|
|
35
35
|
logging_std = importlib.import_module("logging")
|
|
36
36
|
|
|
@@ -338,10 +338,10 @@ class Core:
|
|
|
338
338
|
ulimit_check = check_file_count_against_ulimit(file_count)
|
|
339
339
|
if ulimit_check["can_check"]:
|
|
340
340
|
if ulimit_check["would_exceed"]:
|
|
341
|
-
log.
|
|
342
|
-
log.
|
|
343
|
-
log.
|
|
344
|
-
log.
|
|
341
|
+
log.debug(f"Found {file_count} manifest files, which may exceed the file descriptor limit (ulimit -n = {ulimit_check['soft_limit']})")
|
|
342
|
+
log.debug(f"Available file descriptors: {ulimit_check['available_fds']} (after {ulimit_check['buffer_size']} buffer)")
|
|
343
|
+
log.debug(f"Recommendation: {ulimit_check['recommendation']}")
|
|
344
|
+
log.debug("This may cause 'Too many open files' errors during processing")
|
|
345
345
|
else:
|
|
346
346
|
log.debug(f"File count ({file_count}) is within file descriptor limit ({ulimit_check['soft_limit']})")
|
|
347
347
|
else:
|
|
@@ -434,37 +434,29 @@ class Core:
|
|
|
434
434
|
return ''.join(f'[{char.lower()}{char.upper()}]' if char.isalpha() else char for char in input_string)
|
|
435
435
|
|
|
436
436
|
@staticmethod
|
|
437
|
-
def empty_head_scan_file() ->
|
|
438
|
-
# Create an empty file for when no head full scan so that the diff endpoint can always be used
|
|
439
|
-
empty_file_obj = io.BytesIO(b"")
|
|
440
|
-
empty_filename = "initial_head_scan"
|
|
441
|
-
empty_full_scan_file = [(empty_filename, (empty_filename, empty_file_obj))]
|
|
442
|
-
return empty_full_scan_file
|
|
443
|
-
|
|
444
|
-
@staticmethod
|
|
445
|
-
def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str, Tuple[str, BinaryIO]]]:
|
|
437
|
+
def empty_head_scan_file() -> List[str]:
|
|
446
438
|
"""
|
|
447
|
-
|
|
439
|
+
Creates a temporary empty file for baseline scans when no head scan exists.
|
|
448
440
|
|
|
449
|
-
This version uses lazy file loading to prevent "Too many open files" errors
|
|
450
|
-
when processing large numbers of manifest files.
|
|
451
|
-
|
|
452
|
-
Args:
|
|
453
|
-
files: List of file paths from find_files()
|
|
454
|
-
workspace: Base directory path to make paths relative to
|
|
455
|
-
|
|
456
441
|
Returns:
|
|
457
|
-
List
|
|
458
|
-
[(field_name, (filename, file_object)), ...]
|
|
442
|
+
List containing path to a temporary empty file
|
|
459
443
|
"""
|
|
460
|
-
|
|
444
|
+
# Create a temporary empty file
|
|
445
|
+
temp_fd, temp_path = tempfile.mkstemp(suffix='.empty', prefix='socket_baseline_')
|
|
446
|
+
|
|
447
|
+
# Close the file descriptor since we just need the path
|
|
448
|
+
# The file is already created and empty
|
|
449
|
+
os.close(temp_fd)
|
|
450
|
+
|
|
451
|
+
log.debug(f"Created temporary empty file for baseline scan: {temp_path}")
|
|
452
|
+
return [temp_path]
|
|
461
453
|
|
|
462
|
-
def create_full_scan(self, files:
|
|
454
|
+
def create_full_scan(self, files: List[str], params: FullScanParams) -> FullScan:
|
|
463
455
|
"""
|
|
464
456
|
Creates a new full scan via the Socket API.
|
|
465
457
|
|
|
466
458
|
Args:
|
|
467
|
-
files: List of
|
|
459
|
+
files: List of file paths to scan
|
|
468
460
|
params: Parameters for the full scan
|
|
469
461
|
|
|
470
462
|
Returns:
|
|
@@ -473,7 +465,7 @@ class Core:
|
|
|
473
465
|
log.info("Creating new full scan")
|
|
474
466
|
create_full_start = time.time()
|
|
475
467
|
|
|
476
|
-
res = self.sdk.fullscans.post(files, params, use_types=True)
|
|
468
|
+
res = self.sdk.fullscans.post(files, params, use_types=True, use_lazy_loading=True, max_open_files=50)
|
|
477
469
|
if not res.success:
|
|
478
470
|
log.error(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
479
471
|
raise Exception(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
@@ -525,14 +517,13 @@ class Core:
|
|
|
525
517
|
if save_manifest_tar_path and files:
|
|
526
518
|
self.save_manifest_tar(files, save_manifest_tar_path, path)
|
|
527
519
|
|
|
528
|
-
files_for_sending = self.load_files_for_sending(files, path)
|
|
529
520
|
if not files:
|
|
530
521
|
return diff
|
|
531
522
|
|
|
532
523
|
try:
|
|
533
524
|
# Create new scan
|
|
534
525
|
new_scan_start = time.time()
|
|
535
|
-
new_full_scan = self.create_full_scan(
|
|
526
|
+
new_full_scan = self.create_full_scan(files, params)
|
|
536
527
|
new_scan_end = time.time()
|
|
537
528
|
log.info(f"Total time to create new full scan: {new_scan_end - new_scan_start:.2f}")
|
|
538
529
|
except APIFailure as e:
|
|
@@ -779,7 +770,15 @@ class Core:
|
|
|
779
770
|
log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan_id}")
|
|
780
771
|
diff_start = time.time()
|
|
781
772
|
try:
|
|
782
|
-
diff_report =
|
|
773
|
+
diff_report = (
|
|
774
|
+
self.sdk.fullscans.stream_diff
|
|
775
|
+
(
|
|
776
|
+
self.config.org_slug,
|
|
777
|
+
head_full_scan_id,
|
|
778
|
+
new_full_scan_id,
|
|
779
|
+
use_types=True
|
|
780
|
+
).data
|
|
781
|
+
)
|
|
783
782
|
except APIFailure as e:
|
|
784
783
|
log.error(f"API Error: {e}")
|
|
785
784
|
sys.exit(1)
|
|
@@ -877,7 +876,6 @@ class Core:
|
|
|
877
876
|
if save_manifest_tar_path and files:
|
|
878
877
|
self.save_manifest_tar(files, save_manifest_tar_path, path)
|
|
879
878
|
|
|
880
|
-
files_for_sending = self.load_files_for_sending(files, path)
|
|
881
879
|
if not files:
|
|
882
880
|
return Diff(id="NO_DIFF_RAN", diff_url="", report_url="")
|
|
883
881
|
|
|
@@ -887,7 +885,9 @@ class Core:
|
|
|
887
885
|
except APIResourceNotFound:
|
|
888
886
|
head_full_scan_id = None
|
|
889
887
|
|
|
888
|
+
# If no head scan exists, create an empty baseline scan
|
|
890
889
|
if head_full_scan_id is None:
|
|
890
|
+
log.info("No previous scan found - creating empty baseline scan")
|
|
891
891
|
new_params = copy.deepcopy(params.__dict__)
|
|
892
892
|
new_params.pop('include_license_details')
|
|
893
893
|
tmp_params = FullScanParams(**new_params)
|
|
@@ -895,13 +895,34 @@ class Core:
|
|
|
895
895
|
tmp_params.tmp = True
|
|
896
896
|
tmp_params.set_as_pending_head = False
|
|
897
897
|
tmp_params.make_default_branch = False
|
|
898
|
-
|
|
899
|
-
|
|
898
|
+
|
|
899
|
+
# Create baseline scan with empty file
|
|
900
|
+
empty_files = Core.empty_head_scan_file()
|
|
901
|
+
try:
|
|
902
|
+
head_full_scan = self.create_full_scan(empty_files, tmp_params)
|
|
903
|
+
head_full_scan_id = head_full_scan.id
|
|
904
|
+
log.debug(f"Created empty baseline scan: {head_full_scan_id}")
|
|
905
|
+
|
|
906
|
+
# Clean up the temporary empty file
|
|
907
|
+
for temp_file in empty_files:
|
|
908
|
+
try:
|
|
909
|
+
os.unlink(temp_file)
|
|
910
|
+
log.debug(f"Cleaned up temporary file: {temp_file}")
|
|
911
|
+
except OSError as e:
|
|
912
|
+
log.warning(f"Failed to clean up temporary file {temp_file}: {e}")
|
|
913
|
+
except Exception as e:
|
|
914
|
+
# Clean up temp files even if scan creation fails
|
|
915
|
+
for temp_file in empty_files:
|
|
916
|
+
try:
|
|
917
|
+
os.unlink(temp_file)
|
|
918
|
+
except OSError:
|
|
919
|
+
pass
|
|
920
|
+
raise e
|
|
900
921
|
|
|
901
922
|
# Create new scan
|
|
902
923
|
try:
|
|
903
924
|
new_scan_start = time.time()
|
|
904
|
-
new_full_scan = self.create_full_scan(
|
|
925
|
+
new_full_scan = self.create_full_scan(files, params)
|
|
905
926
|
new_scan_end = time.time()
|
|
906
927
|
log.info(f"Total time to create new full scan: {new_scan_end - new_scan_start:.2f}")
|
|
907
928
|
except APIFailure as e:
|
|
@@ -913,6 +934,7 @@ class Core:
|
|
|
913
934
|
log.error(f"Stack trace:\n{traceback.format_exc()}")
|
|
914
935
|
raise
|
|
915
936
|
|
|
937
|
+
# Handle diff generation - now we always have both scans
|
|
916
938
|
scans_ready = self.check_full_scans_status(head_full_scan_id, new_full_scan.id)
|
|
917
939
|
if scans_ready is False:
|
|
918
940
|
log.error(f"Full scans did not complete within {self.config.timeout} seconds")
|
|
@@ -1134,6 +1156,12 @@ class Core:
|
|
|
1134
1156
|
alert = Alert(**alert_item)
|
|
1135
1157
|
props = getattr(self.config.all_issues, alert.type, default_props)
|
|
1136
1158
|
introduced_by = self.get_source_data(package, packages)
|
|
1159
|
+
|
|
1160
|
+
# Handle special case for license policy violations
|
|
1161
|
+
title = props.title
|
|
1162
|
+
if alert.type == "licenseSpdxDisj" and not title:
|
|
1163
|
+
title = "License Policy Violation"
|
|
1164
|
+
|
|
1137
1165
|
issue_alert = Issue(
|
|
1138
1166
|
pkg_type=package.type,
|
|
1139
1167
|
pkg_name=package.name,
|
|
@@ -1144,7 +1172,7 @@ class Core:
|
|
|
1144
1172
|
type=alert.type,
|
|
1145
1173
|
severity=alert.severity,
|
|
1146
1174
|
description=props.description,
|
|
1147
|
-
title=
|
|
1175
|
+
title=title,
|
|
1148
1176
|
suggestion=props.suggestion,
|
|
1149
1177
|
next_step_title=props.nextStepTitle,
|
|
1150
1178
|
introduced_by=introduced_by,
|
|
@@ -1156,11 +1184,10 @@ class Core:
|
|
|
1156
1184
|
action = self.config.security_policy[alert.type]['action']
|
|
1157
1185
|
setattr(issue_alert, action, True)
|
|
1158
1186
|
|
|
1159
|
-
if issue_alert.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
alerts_collection[issue_alert.key].append(issue_alert)
|
|
1187
|
+
if issue_alert.key not in alerts_collection:
|
|
1188
|
+
alerts_collection[issue_alert.key] = [issue_alert]
|
|
1189
|
+
else:
|
|
1190
|
+
alerts_collection[issue_alert.key].append(issue_alert)
|
|
1164
1191
|
|
|
1165
1192
|
return alerts_collection
|
|
1166
1193
|
|
|
@@ -1232,7 +1259,8 @@ class Core:
|
|
|
1232
1259
|
if alert_key not in removed_package_alerts:
|
|
1233
1260
|
new_alerts = added_package_alerts[alert_key]
|
|
1234
1261
|
for alert in new_alerts:
|
|
1235
|
-
|
|
1262
|
+
# Consolidate by package and alert type, not by manifest details
|
|
1263
|
+
alert_str = f"{alert.purl},{alert.type}"
|
|
1236
1264
|
|
|
1237
1265
|
if alert.error or alert.warn:
|
|
1238
1266
|
if alert_str not in consolidated_alerts:
|
|
@@ -1243,7 +1271,8 @@ class Core:
|
|
|
1243
1271
|
removed_alerts = removed_package_alerts[alert_key]
|
|
1244
1272
|
|
|
1245
1273
|
for alert in new_alerts:
|
|
1246
|
-
|
|
1274
|
+
# Consolidate by package and alert type, not by manifest details
|
|
1275
|
+
alert_str = f"{alert.purl},{alert.type}"
|
|
1247
1276
|
|
|
1248
1277
|
# Only add if:
|
|
1249
1278
|
# 1. Alert isn't in removed packages (or we're not ignoring readded alerts)
|