Skip to content

Commit b254122

Browse files
authored
CM-52972 - Add pre-push hook support (#346)
1 parent b5b2591 commit b254122

File tree

10 files changed

+880
-24
lines changed

10 files changed

+880
-24
lines changed

.pre-commit-hooks.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,24 @@
1616
language_version: python3
1717
entry: cycode
1818
args: [ '-o', 'text', '--no-progress-meter', 'scan', '-t', 'sast', 'pre-commit' ]
19+
- id: cycode-pre-push
20+
name: Cycode Secrets pre-push defender
21+
language: python
22+
language_version: python3
23+
entry: cycode
24+
args: [ '-o', 'text', '--no-progress-meter', 'scan', '-t', 'secret', 'pre-push' ]
25+
stages: [pre-push]
26+
- id: cycode-sca-pre-push
27+
name: Cycode SCA pre-push defender
28+
language: python
29+
language_version: python3
30+
entry: cycode
31+
args: [ '-o', 'text', '--no-progress-meter', 'scan', '-t', 'sca', 'pre-push' ]
32+
stages: [pre-push]
33+
- id: cycode-sast-pre-push
34+
name: Cycode SAST pre-push defender
35+
language: python
36+
language_version: python3
37+
entry: cycode
38+
args: [ '-o', 'text', '--no-progress-meter', 'scan', '-t', 'sast', 'pre-push' ]
39+
stages: [pre-push]

README.md

Lines changed: 143 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This guide walks you through both installation and usage.
3737
4. [Commit History Scan](#commit-history-scan)
3838
1. [Commit Range Option (Diff Scanning)](#commit-range-option-diff-scanning)
3939
5. [Pre-Commit Scan](#pre-commit-scan)
40+
6. [Pre-Push Scan](#pre-push-scan)
4041
2. [Scan Results](#scan-results)
4142
1. [Show/Hide Secrets](#showhide-secrets)
4243
2. [Soft Fail](#soft-fail)
@@ -213,13 +214,15 @@ export CYCODE_CLIENT_SECRET={your Cycode Secret Key}
213214

214215
## Install Pre-Commit Hook
215216

216-
Cycodes pre-commit hook can be set up within your local repository so that the Cycode CLI application will identify any issues with your code automatically before you commit it to your codebase.
217+
Cycode's pre-commit and pre-push hooks can be set up within your local repository so that the Cycode CLI application will identify any issues with your code automatically before you commit or push it to your codebase.
217218
218219
> [!NOTE]
219-
> pre-commit hook is not available for IaC scans.
220+
> pre-commit and pre-push hooks are not available for IaC scans.
220221
221222
Perform the following steps to install the pre-commit hook:
222223
224+
### Installing Pre-Commit Hook
225+
223226
1. Install the pre-commit framework (Python 3.9 or higher must be installed):
224227
225228
```bash
@@ -233,29 +236,25 @@ Perform the following steps to install the pre-commit hook:
233236
```yaml
234237
repos:
235238
- repo: https://github.com/cycodehq/cycode-cli
236-
rev: v3.4.2
239+
rev: v3.5.0
237240
hooks:
238241
- id: cycode
239-
stages:
240-
- pre-commit
242+
stages: [pre-commit]
241243
```
242244
243245
4. Modify the created file for your specific needs. Use hook ID `cycode` to enable scan for Secrets. Use hook ID `cycode-sca` to enable SCA scan. Use hook ID `cycode-sast` to enable SAST scan. If you want to enable all scanning types, use this configuration:
244246
245247
```yaml
246248
repos:
247249
- repo: https://github.com/cycodehq/cycode-cli
248-
rev: v3.4.2
250+
rev: v3.5.0
249251
hooks:
250252
- id: cycode
251-
stages:
252-
- pre-commit
253+
stages: [pre-commit]
253254
- id: cycode-sca
254-
stages:
255-
- pre-commit
255+
stages: [pre-commit]
256256
- id: cycode-sast
257-
stages:
258-
- pre-commit
257+
stages: [pre-commit]
259258
```
260259
261260
5. Install Cycode’s hook:
@@ -278,6 +277,37 @@ Perform the following steps to install the pre-commit hook:
278277
> Trigger happens on `git commit` command.
279278
> Hook triggers only on the files that are staged for commit.
280279
280+
### Installing Pre-Push Hook
281+
282+
To install the pre-push hook in addition to or instead of the pre-commit hook:
283+
284+
1. Add the pre-push hooks to your `.pre-commit-config.yaml` file:
285+
286+
```yaml
287+
repos:
288+
- repo: https://github.com/cycodehq/cycode-cli
289+
rev: v3.5.0
290+
hooks:
291+
- id: cycode-pre-push
292+
stages: [pre-push]
293+
```
294+
295+
2. Install the pre-push hook:
296+
297+
```bash
298+
pre-commit install --hook-type pre-push
299+
```
300+
301+
3. For both pre-commit and pre-push hooks, use:
302+
303+
```bash
304+
pre-commit install
305+
pre-commit install --hook-type pre-push
306+
```
307+
308+
> [!NOTE]
309+
> Pre-push hooks trigger on `git push` command and scan only the commits about to be pushed.
310+
281311
# Cycode CLI Commands
282312
283313
The following are the options and commands available with the Cycode CLI application:
@@ -786,6 +816,107 @@ After installing the pre-commit hook, you may occasionally wish to skip scanning
786816
SKIP=cycode git commit -m <your commit message>`
787817
```
788818
819+
### Pre-Push Scan
820+
821+
A pre-push scan automatically identifies any issues before you push changes to the remote repository. This hook runs on the client side and scans only the commits that are about to be pushed, making it efficient for catching issues before they reach the remote repository.
822+
823+
> [!NOTE]
824+
> Pre-push hook is not available for IaC scans.
825+
826+
The pre-push hook integrates with the pre-commit framework and can be configured to run before any `git push` operation.
827+
828+
#### Installing Pre-Push Hook
829+
830+
To set up the pre-push hook using the pre-commit framework:
831+
832+
1. Install the pre-commit framework (if not already installed):
833+
834+
```bash
835+
pip3 install pre-commit
836+
```
837+
838+
2. Create or update your `.pre-commit-config.yaml` file to include the pre-push hooks:
839+
840+
```yaml
841+
repos:
842+
- repo: https://github.com/cycodehq/cycode-cli
843+
rev: v3.5.0
844+
hooks:
845+
- id: cycode-pre-push
846+
stages: [pre-push]
847+
```
848+
849+
3. For multiple scan types, use this configuration:
850+
851+
```yaml
852+
repos:
853+
- repo: https://github.com/cycodehq/cycode-cli
854+
rev: v3.5.0
855+
hooks:
856+
- id: cycode-pre-push # Secrets scan
857+
stages: [pre-push]
858+
- id: cycode-sca-pre-push # SCA scan
859+
stages: [pre-push]
860+
- id: cycode-sast-pre-push # SAST scan
861+
stages: [pre-push]
862+
```
863+
864+
4. Install the pre-push hook:
865+
866+
```bash
867+
pre-commit install --hook-type pre-push
868+
```
869+
870+
A successful installation will result in the message: `Pre-push installed at .git/hooks/pre-push`.
871+
872+
5. Keep the pre-push hook up to date:
873+
874+
```bash
875+
pre-commit autoupdate
876+
```
877+
878+
#### How Pre-Push Scanning Works
879+
880+
The pre-push hook:
881+
- Receives information about what commits are being pushed
882+
- Calculates the appropriate commit range to scan
883+
- For new branches: scans all commits from the merge base with the default branch
884+
- For existing branches: scans only the new commits since the last push
885+
- Runs the same comprehensive scanning as other Cycode scan modes
886+
887+
#### Smart Default Branch Detection
888+
889+
The pre-push hook intelligently detects the default branch for merge base calculation using this priority order:
890+
891+
1. **Environment Variable**: `CYCODE_DEFAULT_BRANCH` - allows manual override
892+
2. **Git Remote HEAD**: Uses `git symbolic-ref refs/remotes/origin/HEAD` to detect the actual remote default branch
893+
3. **Git Remote Info**: Falls back to `git remote show origin` if symbolic-ref fails
894+
4. **Hardcoded Fallbacks**: Uses common default branch names (origin/main, origin/master, main, master)
895+
896+
**Setting a Custom Default Branch:**
897+
```bash
898+
export CYCODE_DEFAULT_BRANCH=origin/develop
899+
```
900+
901+
This smart detection ensures the pre-push hook works correctly regardless of whether your repository uses `main`, `master`, `develop`, or any other default branch name.
902+
903+
#### Skipping Pre-Push Scans
904+
905+
To skip the pre-push scan for a specific push operation, use:
906+
907+
```bash
908+
SKIP=cycode-pre-push git push
909+
```
910+
911+
Or to skip all pre-push hooks:
912+
913+
```bash
914+
git push --no-verify
915+
```
916+
917+
> [!TIP]
918+
> The pre-push hook is triggered on `git push` command and scans only the commits that are about to be pushed, making it more efficient than scanning the entire repository.
919+
789920
## Scan Results
790921
791922
Each scan will complete with a message stating if any issues were found or not.

cycode/cli/apps/scan/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
from cycode.cli.apps.scan.commit_history.commit_history_command import commit_history_command
44
from cycode.cli.apps.scan.path.path_command import path_command
55
from cycode.cli.apps.scan.pre_commit.pre_commit_command import pre_commit_command
6+
from cycode.cli.apps.scan.pre_push.pre_push_command import pre_push_command
67
from cycode.cli.apps.scan.pre_receive.pre_receive_command import pre_receive_command
78
from cycode.cli.apps.scan.repository.repository_command import repository_command
89
from cycode.cli.apps.scan.scan_command import scan_command, scan_command_result_callback
910

1011
app = typer.Typer(name='scan', no_args_is_help=True)
1112

13+
_AUTOMATION_COMMANDS_RICH_HELP_PANEL = 'Automation commands'
14+
1215
_scan_command_docs = 'https://github.com/cycodehq/cycode-cli/blob/main/README.md#scan-command'
1316
_scan_command_epilog = f'[bold]Documentation:[/] [link={_scan_command_docs}]{_scan_command_docs}[/link]'
1417

@@ -26,16 +29,22 @@
2629
app.command(
2730
name='pre-commit',
2831
short_help='Use this command in pre-commit hook to scan any content that was not committed yet.',
29-
rich_help_panel='Automation commands',
32+
rich_help_panel=_AUTOMATION_COMMANDS_RICH_HELP_PANEL,
3033
)(pre_commit_command)
34+
app.command(
35+
name='pre-push',
36+
short_help='Use this command in pre-push hook to scan commits before pushing them to the remote repository.',
37+
rich_help_panel=_AUTOMATION_COMMANDS_RICH_HELP_PANEL,
38+
)(pre_push_command)
3139
app.command(
3240
name='pre-receive',
3341
short_help='Use this command in pre-receive hook '
3442
'to scan commits on the server side before pushing them to the repository.',
35-
rich_help_panel='Automation commands',
43+
rich_help_panel=_AUTOMATION_COMMANDS_RICH_HELP_PANEL,
3644
)(pre_receive_command)
3745

3846
# backward compatibility
3947
app.command(hidden=True, name='commit_history')(commit_history_command)
4048
app.command(hidden=True, name='pre_commit')(pre_commit_command)
49+
app.command(hidden=True, name='pre_push')(pre_push_command)
4150
app.command(hidden=True, name='pre_receive')(pre_receive_command)

cycode/cli/apps/scan/pre_push/__init__.py

Whitespace-only changes.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import logging
2+
import os
3+
from typing import Annotated, Optional
4+
5+
import typer
6+
7+
from cycode.cli import consts
8+
from cycode.cli.apps.scan.commit_range_scanner import (
9+
is_verbose_mode_requested_in_pre_receive_scan,
10+
scan_commit_range,
11+
should_skip_pre_receive_scan,
12+
)
13+
from cycode.cli.config import configuration_manager
14+
from cycode.cli.console import console
15+
from cycode.cli.exceptions.handle_scan_errors import handle_scan_exception
16+
from cycode.cli.files_collector.commit_range_documents import (
17+
calculate_pre_push_commit_range,
18+
parse_pre_push_input,
19+
)
20+
from cycode.cli.logger import logger
21+
from cycode.cli.utils import scan_utils
22+
from cycode.cli.utils.sentry import add_breadcrumb
23+
from cycode.cli.utils.task_timer import TimeoutAfter
24+
from cycode.logger import set_logging_level
25+
26+
27+
def pre_push_command(
28+
ctx: typer.Context,
29+
_: Annotated[Optional[list[str]], typer.Argument(help='Ignored arguments', hidden=True)] = None,
30+
) -> None:
31+
try:
32+
add_breadcrumb('pre_push')
33+
34+
if should_skip_pre_receive_scan():
35+
logger.info(
36+
'A scan has been skipped as per your request. '
37+
'Please note that this may leave your system vulnerable to secrets that have not been detected.'
38+
)
39+
return
40+
41+
if is_verbose_mode_requested_in_pre_receive_scan():
42+
ctx.obj['verbose'] = True
43+
set_logging_level(logging.DEBUG)
44+
logger.debug('Verbose mode enabled: all log levels will be displayed.')
45+
46+
command_scan_type = ctx.info_name
47+
timeout = configuration_manager.get_pre_push_command_timeout(command_scan_type)
48+
with TimeoutAfter(timeout):
49+
push_update_details = parse_pre_push_input()
50+
commit_range = calculate_pre_push_commit_range(push_update_details)
51+
if not commit_range:
52+
logger.info(
53+
'No new commits found for pushed branch, %s',
54+
{'push_update_details': push_update_details},
55+
)
56+
return
57+
58+
scan_commit_range(
59+
ctx=ctx,
60+
repo_path=os.getcwd(),
61+
commit_range=commit_range,
62+
max_commits_count=configuration_manager.get_pre_push_max_commits_to_scan_count(command_scan_type),
63+
)
64+
65+
if scan_utils.is_scan_failed(ctx):
66+
console.print(consts.PRE_RECEIVE_AND_PUSH_REMEDIATION_MESSAGE)
67+
except Exception as e:
68+
handle_scan_exception(ctx, e)

cycode/cli/apps/scan/pre_receive/pre_receive_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,6 @@ def pre_receive_command(
6363
)
6464

6565
if scan_utils.is_scan_failed(ctx):
66-
console.print(consts.PRE_RECEIVE_REMEDIATION_MESSAGE)
66+
console.print(consts.PRE_RECEIVE_AND_PUSH_REMEDIATION_MESSAGE)
6767
except Exception as e:
6868
handle_scan_exception(ctx, e)

cycode/cli/consts.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,14 @@
240240
DEFAULT_PRE_RECEIVE_MAX_COMMITS_TO_SCAN_COUNT = 50
241241
PRE_RECEIVE_COMMAND_TIMEOUT_ENV_VAR_NAME = 'PRE_RECEIVE_COMMAND_TIMEOUT'
242242
DEFAULT_PRE_RECEIVE_COMMAND_TIMEOUT_IN_SECONDS = 60
243-
PRE_RECEIVE_REMEDIATION_MESSAGE = """
243+
# pre push scan
244+
PRE_PUSH_MAX_COMMITS_TO_SCAN_COUNT_ENV_VAR_NAME = 'PRE_PUSH_MAX_COMMITS_TO_SCAN_COUNT'
245+
DEFAULT_PRE_PUSH_MAX_COMMITS_TO_SCAN_COUNT = 50
246+
PRE_PUSH_COMMAND_TIMEOUT_ENV_VAR_NAME = 'PRE_PUSH_COMMAND_TIMEOUT'
247+
DEFAULT_PRE_PUSH_COMMAND_TIMEOUT_IN_SECONDS = 60
248+
CYCODE_DEFAULT_BRANCH_ENV_VAR_NAME = 'CYCODE_DEFAULT_BRANCH'
249+
# pre push and pre receive common
250+
PRE_RECEIVE_AND_PUSH_REMEDIATION_MESSAGE = """
244251
Cycode Secrets Push Protection
245252
------------------------------------------------------------------------------
246253
Resolve the following secrets by rewriting your local commit history before pushing again.

0 commit comments

Comments
 (0)