Skip to content

Commit c802c34

Browse files
committed
Merge remote-tracking branch 'upstream/main'
2 parents 29b45fd + 5457fd1 commit c802c34

21 files changed

+626
-100
lines changed

.github/workflows/docker-image.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ jobs:
6464
uses: docker/setup-buildx-action@v3
6565

6666
- name: Login to Docker Hub
67+
if: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') }}
6768
uses: docker/login-action@v3
6869
with:
6970
username: ${{ secrets.DOCKERHUB_USER }}

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM python:3.12.9-alpine3.21 AS base
22
WORKDIR /usr/cycode/app
3-
RUN apk add git=2.47.2-r0
3+
RUN apk add git=2.47.3-r0
44

55
FROM base AS builder
66
ENV POETRY_VERSION=1.8.3

README.md

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ This guide walks you through both installation and usage.
3535
3. [Path Scan](#path-scan)
3636
1. [Terraform Plan Scan](#terraform-plan-scan)
3737
4. [Commit History Scan](#commit-history-scan)
38-
1. [Commit Range Option](#commit-range-option)
38+
1. [Commit Range Option (Diff Scanning)](#commit-range-option-diff-scanning)
3939
5. [Pre-Commit Scan](#pre-commit-scan)
4040
2. [Scan Results](#scan-results)
4141
1. [Show/Hide Secrets](#showhide-secrets)
@@ -538,25 +538,26 @@ This information can be helpful when:
538538
539539
The Cycode CLI application offers several types of scans so that you can choose the option that best fits your case. The following are the current options and commands available:
540540
541-
| Option | Description |
542-
|------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
543-
| `-t, --scan-type [secret\|iac\|sca\|sast]` | Specify the scan you wish to execute (`secret`/`iac`/`sca`/`sast`), the default is `secret`. |
544-
| `--show-secret BOOLEAN` | Show secrets in plain text. See [Show/Hide Secrets](#showhide-secrets) section for more details. |
545-
| `--soft-fail BOOLEAN` | Run scan without failing, always return a non-error status code. See [Soft Fail](#soft-fail) section for more details. |
546-
| `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher. |
547-
| `--sca-scan` | Specify the SCA scan you wish to execute (`package-vulnerabilities`/`license-compliance`). The default is both. |
548-
| `--monitor` | When specified, the scan results will be recorded in Cycode. |
549-
| `--cycode-report` | Display a link to the scan report in the Cycode platform in the console output. |
550-
| `--no-restore` | When specified, Cycode will not run the restore command. This will scan direct dependencies ONLY! |
551-
| `--gradle-all-sub-projects` | Run gradle restore command for all sub projects. This should be run from the project root directory ONLY! |
552-
| `--help` | Show options for given command. |
553-
554-
| Command | Description |
555-
|----------------------------------------|-----------------------------------------------------------------|
556-
| [commit-history](#commit-history-scan) | Scan all the commits history in this git repository |
557-
| [path](#path-scan) | Scan the files in the path supplied in the command |
558-
| [pre-commit](#pre-commit-scan) | Use this command to scan the content that was not committed yet |
559-
| [repository](#repository-scan) | Scan git repository including its history |
541+
| Option | Description |
542+
|------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
543+
| `-t, --scan-type [secret\|iac\|sca\|sast]` | Specify the scan you wish to execute (`secret`/`iac`/`sca`/`sast`), the default is `secret`. |
544+
| `--show-secret BOOLEAN` | Show secrets in plain text. See [Show/Hide Secrets](#showhide-secrets) section for more details. |
545+
| `--soft-fail BOOLEAN` | Run scan without failing, always return a non-error status code. See [Soft Fail](#soft-fail) section for more details. |
546+
| `--severity-threshold [INFO\|LOW\|MEDIUM\|HIGH\|CRITICAL]` | Show only violations at the specified level or higher. |
547+
| `--sca-scan` | Specify the SCA scan you wish to execute (`package-vulnerabilities`/`license-compliance`). The default is both. |
548+
| `--monitor` | When specified, the scan results will be recorded in Cycode. |
549+
| `--cycode-report` | Display a link to the scan report in the Cycode platform in the console output. |
550+
| `--no-restore` | When specified, Cycode will not run the restore command. This will scan direct dependencies ONLY! |
551+
| `--gradle-all-sub-projects` | Run gradle restore command for all sub projects. This should be run from |
552+
| `--maven-settings-file` | For Maven only, allows using a custom [settings.xml](https://maven.apache.org/settings.html) file when scanning for dependencies |
553+
| `--help` | Show options for given command. |
554+
555+
| Command | Description |
556+
|----------------------------------------|-----------------------------------------------------------------------|
557+
| [commit-history](#commit-history-scan) | Scan commit history or perform diff scanning between specific commits |
558+
| [path](#path-scan) | Scan the files in the path supplied in the command |
559+
| [pre-commit](#pre-commit-scan) | Use this command to scan the content that was not committed yet |
560+
| [repository](#repository-scan) | Scan git repository including its history |
560561
561562
### Options
562563
@@ -700,9 +701,16 @@ If you just have a configuration file, you can generate a plan by doing the foll
700701
### Commit History Scan
701702
702703
> [!NOTE]
703-
> Secrets scanning analyzes all commits in the repository history because secrets introduced and later removed can still be leaked or exposed. SCA and SAST scanning focus only on the latest code state and the changes between branches or pull requests. Full commit history scanning is not performed for SCA and SAST.
704+
> Commit History Scan is not available for IaC scans.
704705
705-
A commit history scan is limited to a local repository’s previous commits, focused on finding any secrets within the commit history, instead of examining the repository’s current state.
706+
The commit history scan command provides two main capabilities:
707+
708+
1. **Full History Scanning**: Analyze all commits in the repository history
709+
2. **Diff Scanning**: Scan only the changes between specific commits
710+
711+
Secrets scanning can analyze all commits in the repository history because secrets introduced and later removed can still be leaked or exposed. For SCA and SAST scans, the commit history command focuses on scanning the differences/changes between commits, making it perfect for pull request reviews and incremental scanning.
712+
713+
A commit history scan examines your Git repository's commit history and can be used both for comprehensive historical analysis and targeted diff scanning of specific changes.
706714

707715
To execute a commit history scan, execute the following:
708716

@@ -718,13 +726,55 @@ The following options are available for use with this command:
718726
|---------------------------|----------------------------------------------------------------------------------------------------------|
719727
| `-r, --commit-range TEXT` | Scan a commit range in this git repository, by default cycode scans all commit history (example: HEAD~1) |
720728

721-
#### Commit Range Option
729+
#### Commit Range Option (Diff Scanning)
730+
731+
The commit range option enables **diff scanning** – scanning only the changes between specific commits instead of the entire repository history.
732+
This is particularly useful for:
733+
- **Pull request validation**: Scan only the changes introduced in a PR
734+
- **Incremental CI/CD scanning**: Focus on recent changes rather than the entire codebase
735+
- **Feature branch review**: Compare changes against main/master branch
736+
- **Performance optimization**: Faster scans by limiting scope to relevant changes
737+
738+
#### Commit Range Syntax
739+
740+
The `--commit-range` (`-r`) option supports standard Git revision syntax:
741+
742+
| Syntax | Description | Example |
743+
|---------------------|-----------------------------------|-------------------------|
744+
| `commit1..commit2` | Changes from commit1 to commit2 | `abc123..def456` |
745+
| `commit1...commit2` | Changes in commit2 not in commit1 | `main...feature-branch` |
746+
| `commit` | Changes from commit to HEAD | `HEAD~1` |
747+
| `branch1..branch2` | Changes from branch1 to branch2 | `main..feature-branch` |
748+
749+
#### Diff Scanning Examples
750+
751+
**Scan changes in the last commit:**
752+
```bash
753+
cycode scan commit-history -r HEAD~1 ~/home/git/codebase
754+
```
755+
756+
**Scan changes between two specific commits:**
757+
```bash
758+
cycode scan commit-history -r abc123..def456 ~/home/git/codebase
759+
```
722760

723-
The commit history scan, by default, examines the repository’s entire commit history, all the way back to the initial commit. You can instead limit the scan to a specific commit range by adding the argument `--commit-range` (`-r`) followed by the name you specify.
761+
**Scan changes in your feature branch compared to main:**
762+
```bash
763+
cycode scan commit-history -r main..HEAD ~/home/git/codebase
764+
```
724765

725-
Consider the previous example. If you wanted to scan only specific commits in your repository, you could execute the following:
766+
**Scan changes between main and a feature branch:**
767+
```bash
768+
cycode scan commit-history -r main..feature-branch ~/home/git/codebase
769+
```
726770

727-
`cycode scan commit-history -r {{from-commit-id}}...{{to-commit-id}} ~/home/git/codebase`
771+
**Scan all changes in the last 3 commits:**
772+
```bash
773+
cycode scan commit-history -r HEAD~3..HEAD ~/home/git/codebase
774+
```
775+
776+
> [!TIP]
777+
> For CI/CD pipelines, you can use environment variables like `${{ github.event.pull_request.base.sha }}..${{ github.sha }}` (GitHub Actions) or `$CI_MERGE_REQUEST_TARGET_BRANCH_SHA..$CI_COMMIT_SHA` (GitLab CI) to scan only PR/MR changes.
728778

729779
### Pre-Commit Scan
730780

cycode/cli/apps/scan/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
app.command(name='path', short_help='Scan the files in the paths provided in the command.')(path_command)
2222
app.command(name='repository', short_help='Scan the Git repository included files.')(repository_command)
23-
app.command(name='commit-history', short_help='Scan all the commits history in this Git repository.')(
23+
app.command(name='commit-history', short_help='Scan commit history or perform diff scanning between specific commits.')(
2424
commit_history_command
2525
)
2626
app.command(

cycode/cli/apps/scan/remote_url_resolver.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,46 @@ def _try_to_get_plastic_remote_url(path: str) -> Optional[str]:
9999

100100
def _try_get_git_remote_url(path: str) -> Optional[str]:
101101
try:
102-
remote_url = git_proxy.get_repo(path).remotes[0].config_reader.get('url')
103-
logger.debug('Found Git remote URL, %s', {'remote_url': remote_url, 'path': path})
102+
repo = git_proxy.get_repo(path, search_parent_directories=True)
103+
remote_url = repo.remotes[0].config_reader.get('url')
104+
logger.debug('Found Git remote URL, %s', {'remote_url': remote_url, 'repo_path': repo.working_dir})
104105
return remote_url
105-
except Exception:
106-
logger.debug('Failed to get Git remote URL. Probably not a Git repository')
106+
except Exception as e:
107+
logger.debug('Failed to get Git remote URL. Probably not a Git repository', exc_info=e)
107108
return None
108109

109110

110-
def try_get_any_remote_url(path: str) -> Optional[str]:
111+
def _try_get_any_remote_url(path: str) -> Optional[str]:
111112
remote_url = _try_get_git_remote_url(path)
112113
if not remote_url:
113114
remote_url = _try_to_get_plastic_remote_url(path)
114115

115116
return remote_url
117+
118+
119+
def get_remote_url_scan_parameter(paths: tuple[str, ...]) -> Optional[str]:
120+
remote_urls = set()
121+
for path in paths:
122+
# FIXME(MarshalX): perf issue. This looping will produce:
123+
# - len(paths) Git subprocess calls in the worst case
124+
# - len(paths)*2 Plastic SCM subprocess calls
125+
remote_url = _try_get_any_remote_url(path)
126+
if remote_url:
127+
remote_urls.add(remote_url)
128+
129+
if len(remote_urls) == 1:
130+
# we are resolving remote_url only if all paths belong to the same repo (identical remote URLs),
131+
# otherwise, the behavior is undefined
132+
remote_url = remote_urls.pop()
133+
134+
logger.debug(
135+
'Single remote URL found. Scan will be associated with organization, %s', {'remote_url': remote_url}
136+
)
137+
return remote_url
138+
139+
logger.debug(
140+
'Multiple different remote URLs found. Scan will not be associated with organization, %s',
141+
{'remote_urls': remote_urls},
142+
)
143+
144+
return None

cycode/cli/apps/scan/scan_command.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ def scan_command(
8888
rich_help_panel=_SCA_RICH_HELP_PANEL,
8989
),
9090
] = False,
91+
maven_settings_file: Annotated[
92+
Optional[Path],
93+
typer.Option(
94+
'--maven-settings-file',
95+
show_default=False,
96+
help='When specified, Cycode will use this settings.xml file when building the maven dependency tree.',
97+
dir_okay=False,
98+
rich_help_panel=_SCA_RICH_HELP_PANEL,
99+
),
100+
] = None,
91101
export_type: Annotated[
92102
ExportTypeOption,
93103
typer.Option(
@@ -143,7 +153,10 @@ def scan_command(
143153
ctx.obj['sync'] = sync
144154
ctx.obj['severity_threshold'] = severity_threshold
145155
ctx.obj['monitor'] = monitor
156+
ctx.obj['maven_settings_file'] = maven_settings_file
146157
ctx.obj['report'] = report
158+
ctx.obj['gradle_all_sub_projects'] = gradle_all_sub_projects
159+
ctx.obj['no_restore'] = no_restore
147160

148161
scan_client = get_scan_cycode_client(ctx)
149162
ctx.obj['client'] = scan_client
@@ -156,8 +169,6 @@ def scan_command(
156169
console_printer = ctx.obj['console_printer']
157170
console_printer.enable_recording(export_type, export_file)
158171

159-
_ = no_restore, gradle_all_sub_projects # they are actually used; via ctx.params
160-
161172
_sca_scan_to_context(ctx, sca_scan)
162173

163174

cycode/cli/apps/scan/scan_parameters.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import os
21
from typing import Optional
32

43
import typer
54

6-
from cycode.cli.apps.scan.remote_url_resolver import try_get_any_remote_url
5+
from cycode.cli.apps.scan.remote_url_resolver import get_remote_url_scan_parameter
76
from cycode.cli.utils.scan_utils import generate_unique_scan_id
87
from cycode.logger import get_logger
98

@@ -29,18 +28,9 @@ def get_scan_parameters(ctx: typer.Context, paths: Optional[tuple[str, ...]] = N
2928

3029
scan_parameters['paths'] = paths
3130

32-
if len(paths) != 1:
33-
logger.debug('Multiple paths provided, going to ignore remote url')
34-
return scan_parameters
35-
36-
if not os.path.isdir(paths[0]):
37-
logger.debug('Path is not a directory, going to ignore remote url')
38-
return scan_parameters
39-
40-
remote_url = try_get_any_remote_url(paths[0])
41-
if remote_url:
42-
# TODO(MarshalX): remove hardcode in context
43-
ctx.obj['remote_url'] = remote_url
44-
scan_parameters['remote_url'] = remote_url
31+
remote_url = get_remote_url_scan_parameter(paths)
32+
# TODO(MarshalX): remove hardcode in context
33+
ctx.obj['remote_url'] = remote_url
34+
scan_parameters['remote_url'] = remote_url
4535

4636
return scan_parameters

cycode/cli/consts.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
'package.json',
7373
'package-lock.json',
7474
'yarn.lock',
75+
'deno.lock',
76+
'deno.json',
77+
'pnpm-lock.yaml',
7578
'npm-shrinkwrap.json',
7679
'packages.config',
7780
'project.assets.json',
@@ -102,7 +105,7 @@
102105
'conan.lock',
103106
)
104107

105-
SCA_EXCLUDED_PATHS = (
108+
SCA_EXCLUDED_FOLDER_IN_PATH = (
106109
'node_modules',
107110
'venv',
108111
'.venv',
@@ -126,7 +129,16 @@
126129
'go': ['go.sum', 'go.mod', 'go.mod.graph', 'Gopkg.lock'],
127130
'maven_pom': ['pom.xml'],
128131
'maven_gradle': ['build.gradle', 'build.gradle.kts', 'gradle.lockfile'],
129-
'npm': ['package.json', 'package-lock.json', 'yarn.lock', 'npm-shrinkwrap.json', '.npmrc'],
132+
'npm': [
133+
'package.json',
134+
'package-lock.json',
135+
'yarn.lock',
136+
'npm-shrinkwrap.json',
137+
'.npmrc',
138+
'pnpm-lock.yaml',
139+
'deno.lock',
140+
'deno.json',
141+
],
130142
'nuget': ['packages.config', 'project.assets.json', 'packages.lock.json', 'nuget.config'],
131143
'ruby_gems': ['Gemfile', 'Gemfile.lock'],
132144
'sbt': ['build.sbt', 'build.scala', 'build.sbt.lock'],
@@ -268,10 +280,6 @@
268280
# Result: A -> ... -> C
269281
SCA_SHORTCUT_DEPENDENCY_PATHS = 2
270282

271-
SCA_SKIP_RESTORE_DEPENDENCIES_FLAG = 'no-restore'
272-
273-
SCA_GRADLE_ALL_SUB_PROJECTS_FLAG = 'gradle-all-sub-projects'
274-
275283
PLASTIC_VCS_DATA_SEPARATOR = ':::'
276284
PLASTIC_VSC_CLI_TIMEOUT = 10
277285
PLASTIC_VCS_REMOTE_URI_PREFIX = 'plastic::'

cycode/cli/files_collector/commit_range_documents.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ def get_diff_file_path(diff: 'Diff', relative: bool = False) -> Optional[str]:
193193

194194
if diff.b_blob:
195195
return diff.b_blob.abspath
196-
return diff.a_blob.abspath
196+
if diff.a_blob:
197+
return diff.a_blob.abspath
198+
199+
return None
197200

198201

199202
def get_diff_file_content(diff: 'Diff') -> str:

cycode/cli/files_collector/file_excluder.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from pathlib import Path
12
from typing import TYPE_CHECKING
23

34
from cycode.cli import consts
@@ -40,11 +41,13 @@ def _does_document_exceed_max_size_limit(content: str) -> bool:
4041

4142

4243
def _is_file_relevant_for_sca_scan(filename: str) -> bool:
43-
if any(sca_excluded_path in filename for sca_excluded_path in consts.SCA_EXCLUDED_PATHS):
44-
logger.debug(
45-
'The file is irrelevant because it is from the inner path of node_modules, %s', {'filename': filename}
46-
)
47-
return False
44+
for part in Path(filename).parts:
45+
if part in consts.SCA_EXCLUDED_FOLDER_IN_PATH:
46+
logger.debug(
47+
'The file is irrelevant because it is from an excluded directory, %s',
48+
{'filename': filename, 'excluded_directory': part},
49+
)
50+
return False
4851

4952
return True
5053

0 commit comments

Comments
 (0)