corgea-cli 1.8.4__tar.gz → 1.8.7__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.
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/Cargo.lock +76 -1
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/Cargo.toml +3 -1
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/PKG-INFO +1 -1
- corgea_cli-1.8.7/skills/corgea/SKILL.md +168 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/inspect.rs +3 -3
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/list.rs +4 -4
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/main.rs +4 -2
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scan.rs +78 -13
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/blast.rs +15 -15
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/utils/api.rs +127 -65
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/wait.rs +3 -3
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.github/workflows/npm-publish.yml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.github/workflows/release-binaries.yml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.github/workflows/release.yml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.github/workflows/test.yml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.github/workflows/update_docs..yml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/.gitignore +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/LICENSE +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/README.md +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/bin/corgea.js +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/build_release.sh +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/package.json +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/pyproject.toml +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/scripts/npm/bundle-binaries.js +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/authorize.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/cicd.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/config.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/log.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/fortify.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/parsers/checkmarx.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/parsers/coverity.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/parsers/mod.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/parsers/sarif.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/scanners/parsers/semgrep.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/setup_hooks.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/targets.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/utils/generic.rs +0 -0
- {corgea_cli-1.8.4 → corgea_cli-1.8.7}/src/utils/terminal.rs +0 -0
|
@@ -293,6 +293,35 @@ version = "0.3.1"
|
|
|
293
293
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
294
294
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
|
295
295
|
|
|
296
|
+
[[package]]
|
|
297
|
+
name = "cookie"
|
|
298
|
+
version = "0.18.1"
|
|
299
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
300
|
+
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
|
301
|
+
dependencies = [
|
|
302
|
+
"percent-encoding",
|
|
303
|
+
"time",
|
|
304
|
+
"version_check",
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
[[package]]
|
|
308
|
+
name = "cookie_store"
|
|
309
|
+
version = "0.21.1"
|
|
310
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
311
|
+
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
|
312
|
+
dependencies = [
|
|
313
|
+
"cookie",
|
|
314
|
+
"document-features",
|
|
315
|
+
"idna",
|
|
316
|
+
"log",
|
|
317
|
+
"publicsuffix",
|
|
318
|
+
"serde",
|
|
319
|
+
"serde_derive",
|
|
320
|
+
"serde_json",
|
|
321
|
+
"time",
|
|
322
|
+
"url",
|
|
323
|
+
]
|
|
324
|
+
|
|
296
325
|
[[package]]
|
|
297
326
|
name = "core-foundation"
|
|
298
327
|
version = "0.9.4"
|
|
@@ -311,13 +340,14 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
|
|
311
340
|
|
|
312
341
|
[[package]]
|
|
313
342
|
name = "corgea"
|
|
314
|
-
version = "1.8.
|
|
343
|
+
version = "1.8.7"
|
|
315
344
|
dependencies = [
|
|
316
345
|
"chrono",
|
|
317
346
|
"clap",
|
|
318
347
|
"dirs",
|
|
319
348
|
"git2",
|
|
320
349
|
"globset",
|
|
350
|
+
"http",
|
|
321
351
|
"http-body-util",
|
|
322
352
|
"hyper",
|
|
323
353
|
"hyper-util",
|
|
@@ -478,6 +508,15 @@ dependencies = [
|
|
|
478
508
|
"syn",
|
|
479
509
|
]
|
|
480
510
|
|
|
511
|
+
[[package]]
|
|
512
|
+
name = "document-features"
|
|
513
|
+
version = "0.2.12"
|
|
514
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
515
|
+
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
|
516
|
+
dependencies = [
|
|
517
|
+
"litrs",
|
|
518
|
+
]
|
|
519
|
+
|
|
481
520
|
[[package]]
|
|
482
521
|
name = "either"
|
|
483
522
|
version = "1.15.0"
|
|
@@ -1131,6 +1170,12 @@ version = "0.8.0"
|
|
|
1131
1170
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1132
1171
|
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
|
1133
1172
|
|
|
1173
|
+
[[package]]
|
|
1174
|
+
name = "litrs"
|
|
1175
|
+
version = "1.0.0"
|
|
1176
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1177
|
+
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
|
1178
|
+
|
|
1134
1179
|
[[package]]
|
|
1135
1180
|
name = "lock_api"
|
|
1136
1181
|
version = "0.4.14"
|
|
@@ -1421,6 +1466,22 @@ dependencies = [
|
|
|
1421
1466
|
"unicode-ident",
|
|
1422
1467
|
]
|
|
1423
1468
|
|
|
1469
|
+
[[package]]
|
|
1470
|
+
name = "psl-types"
|
|
1471
|
+
version = "2.0.11"
|
|
1472
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1473
|
+
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
|
1474
|
+
|
|
1475
|
+
[[package]]
|
|
1476
|
+
name = "publicsuffix"
|
|
1477
|
+
version = "2.3.0"
|
|
1478
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1479
|
+
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
|
1480
|
+
dependencies = [
|
|
1481
|
+
"idna",
|
|
1482
|
+
"psl-types",
|
|
1483
|
+
]
|
|
1484
|
+
|
|
1424
1485
|
[[package]]
|
|
1425
1486
|
name = "quick-xml"
|
|
1426
1487
|
version = "0.36.2"
|
|
@@ -1502,6 +1563,8 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
|
|
1502
1563
|
dependencies = [
|
|
1503
1564
|
"base64",
|
|
1504
1565
|
"bytes",
|
|
1566
|
+
"cookie",
|
|
1567
|
+
"cookie_store",
|
|
1505
1568
|
"futures-channel",
|
|
1506
1569
|
"futures-core",
|
|
1507
1570
|
"futures-util",
|
|
@@ -1890,10 +1953,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1890
1953
|
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
|
1891
1954
|
dependencies = [
|
|
1892
1955
|
"deranged",
|
|
1956
|
+
"itoa",
|
|
1893
1957
|
"num-conv",
|
|
1894
1958
|
"powerfmt",
|
|
1895
1959
|
"serde_core",
|
|
1896
1960
|
"time-core",
|
|
1961
|
+
"time-macros",
|
|
1897
1962
|
]
|
|
1898
1963
|
|
|
1899
1964
|
[[package]]
|
|
@@ -1902,6 +1967,16 @@ version = "0.1.8"
|
|
|
1902
1967
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1903
1968
|
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
|
1904
1969
|
|
|
1970
|
+
[[package]]
|
|
1971
|
+
name = "time-macros"
|
|
1972
|
+
version = "0.2.27"
|
|
1973
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1974
|
+
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
|
1975
|
+
dependencies = [
|
|
1976
|
+
"num-conv",
|
|
1977
|
+
"time-core",
|
|
1978
|
+
]
|
|
1979
|
+
|
|
1905
1980
|
[[package]]
|
|
1906
1981
|
name = "tinystr"
|
|
1907
1982
|
version = "0.8.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "corgea"
|
|
3
|
-
version = "1.8.
|
|
3
|
+
version = "1.8.7"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ clap = { version = "4.4.13", features = ["derive"] }
|
|
|
11
11
|
dirs = "5.0.1"
|
|
12
12
|
reqwest = { version = "0.12.23", default-features = false, features = [
|
|
13
13
|
"blocking",
|
|
14
|
+
"cookies",
|
|
14
15
|
"json",
|
|
15
16
|
"multipart",
|
|
16
17
|
"native-tls",
|
|
@@ -34,6 +35,7 @@ chrono = "0.4"
|
|
|
34
35
|
tokio = { version = "1.0", features = ["full"] }
|
|
35
36
|
hyper = { version = "1.0", features = ["full"] }
|
|
36
37
|
hyper-util = { version = "0.1", features = ["full"] }
|
|
38
|
+
http = "1"
|
|
37
39
|
http-body-util = "0.1"
|
|
38
40
|
url = "2.5"
|
|
39
41
|
open = "5.0"
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: corgea
|
|
3
|
+
description: Scans code for security vulnerabilities using Corgea's AI-powered BLAST scanner and third-party tools, manages findings, and displays AI-generated fixes. Use when the user needs to scan for security issues, upload scan reports, list or inspect vulnerabilities, view fixes, or integrate security scanning into CI/CD.
|
|
4
|
+
allowed-tools: Shell, Read, Grep, Glob, StrReplace
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Corgea CLI
|
|
8
|
+
|
|
9
|
+
Find and fix security vulnerabilities using AI-powered scanning (BLAST), third-party scanners, and AI-generated fixes.
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
### Scan — `corgea scan [scanner]`
|
|
14
|
+
|
|
15
|
+
Default scanner is `blast` (AI-powered, server-side). Also supports `semgrep` and `snyk` (must be installed separately), blast should be used by default unless the user asked not to.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
corgea scan # BLAST scan, full project
|
|
19
|
+
corgea scan semgrep # Semgrep scan, upload results
|
|
20
|
+
corgea scan snyk # Snyk Code scan, upload results
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### BLAST Options
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
corgea scan --only-uncommitted # Staged/modified/untracked files only
|
|
27
|
+
corgea scan --target src/,pyproject.toml # Specific paths (comma-separated)
|
|
28
|
+
corgea scan --target "src/**/*.py" # Glob patterns
|
|
29
|
+
corgea scan --target git:diff=origin/main...HEAD # Git diff range
|
|
30
|
+
corgea scan --target git:staged,git:modified # Git selectors
|
|
31
|
+
corgea scan --target - # File list from stdin
|
|
32
|
+
corgea scan --scan-type secrets # Single scan type
|
|
33
|
+
corgea scan --scan-type blast,policy,secrets,pii # Multiple scan types
|
|
34
|
+
corgea scan --scan-type policy --policy 1 # Specific policy ID
|
|
35
|
+
corgea scan --fail-on CR # Exit 1 on critical issues (CR, HI, ME, LO)
|
|
36
|
+
corgea scan --fail # Exit 1 based on project blocking rules
|
|
37
|
+
corgea scan --out-format json --out-file r.json # Export (json, html, sarif, markdown)
|
|
38
|
+
corgea scan --project-name my-service # Override project name
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Scan types: `blast` (base AI), `policy` (PolicyIQ), `malicious`, `secrets`, `pii`.
|
|
42
|
+
|
|
43
|
+
`--only-uncommitted` and `--target` are mutually exclusive. `--fail-on` and `--fail` are mutually exclusive.
|
|
44
|
+
|
|
45
|
+
### Upload — `corgea upload [report]`
|
|
46
|
+
|
|
47
|
+
Upload an existing scan report to Corgea.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
corgea upload path/to/report.json # JSON, SARIF, Coverity XML
|
|
51
|
+
corgea upload path/to/report.fpr # Fortify FPR
|
|
52
|
+
corgea upload report.sarif --project-name svc # Custom project name
|
|
53
|
+
cat report.json | corgea upload # From stdin
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Supported: Semgrep JSON, SARIF, Checkmarx (CLI/Web/XML), Coverity, Fortify FPR.
|
|
57
|
+
|
|
58
|
+
### Wait — `corgea wait [scan_id]`
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
corgea wait # Wait for latest scan
|
|
62
|
+
corgea wait --scan-id SCAN_ID # Wait for specific scan
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### List — `corgea list` (alias: `corgea ls`)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
corgea ls # List scans
|
|
69
|
+
corgea ls --issues --scan-id SCAN_ID # Issues for a scan
|
|
70
|
+
corgea ls --sca-issues # SCA (dependency) issues
|
|
71
|
+
corgea ls --issues --page 2 --page-size 10 # Pagination
|
|
72
|
+
corgea ls --issues --scan-id SCAN_ID --json # JSON output
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
| Flag | Short | Description |
|
|
76
|
+
|------|-------|-------------|
|
|
77
|
+
| `--issues` | `-i` | List code/SAST issues |
|
|
78
|
+
| `--sca-issues` | `-c` | List SCA issues |
|
|
79
|
+
| `--scan-id` | `-s` | Filter to a scan |
|
|
80
|
+
| `--page` | `-p` | Page number |
|
|
81
|
+
| `--page-size` | | Items per page |
|
|
82
|
+
| `--json` | | JSON output |
|
|
83
|
+
|
|
84
|
+
### Inspect — `corgea inspect <id>`
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
corgea inspect SCAN_ID # Scan overview with issue counts
|
|
88
|
+
corgea inspect --issue ISSUE_ID # Full issue details + fix
|
|
89
|
+
corgea inspect --issue --summary ISSUE_ID # Summary only
|
|
90
|
+
corgea inspect --issue --fix ISSUE_ID # Fix explanation only
|
|
91
|
+
corgea inspect --issue --diff ISSUE_ID # Diff only
|
|
92
|
+
corgea inspect --issue --json ISSUE_ID # JSON output
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Flag | Short | Description |
|
|
96
|
+
|------|-------|-------------|
|
|
97
|
+
| `--issue` | `-i` | Treat ID as issue (default: scan) |
|
|
98
|
+
| `--summary` | `-s` | Summary only |
|
|
99
|
+
| `--fix` | `-f` | Fix explanation only |
|
|
100
|
+
| `--diff` | `-d` | Diff only |
|
|
101
|
+
| `--json` | | JSON output |
|
|
102
|
+
|
|
103
|
+
### Setup Hooks — `corgea setup-hooks`
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
corgea setup-hooks # Interactive configuration
|
|
107
|
+
corgea setup-hooks --default-config # Default: secrets + PII, fail on LO
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Installs a pre-commit hook running `corgea scan blast --only-uncommitted`. Bypass with `git commit --no-verify`.
|
|
111
|
+
|
|
112
|
+
## Common Workflows
|
|
113
|
+
|
|
114
|
+
### Scan full project
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
corgea scan
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Scan uncommitted changes
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
corgea scan --only-uncommitted --fail-on HI
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Scan a PR diff
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
corgea scan --target git:diff=origin/main...HEAD --fail-on CR
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Review and apply a fix
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
corgea ls --issues --scan-id SCAN_ID
|
|
136
|
+
corgea inspect --issue --diff ISSUE_ID
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### CI/CD pipeline
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
corgea scan --fail-on CR --out-format sarif --out-file results.sarif
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Upload third-party reports
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
corgea upload report.json --project-name my-app
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Export results
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
corgea scan --out-format html --out-file report.html
|
|
155
|
+
corgea scan --out-format sarif --out-file report.sarif
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Severity Levels
|
|
159
|
+
|
|
160
|
+
`CR` (Critical), `HI` (High), `ME` (Medium), `LO` (Low)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
## Troubleshooting
|
|
165
|
+
|
|
166
|
+
- **"token invalid" or authentication errors**: The user needs to authenticate with Corgea. Ask them to run `corgea login` (browser OAuth) or `corgea login <API_TOKEN>` to set up credentials. For single-tenant instances, use `corgea login --url https://<instance>.corgea.app <TOKEN>`. Tokens can also be set via the `CORGEA_API_TOKEN` environment variable.
|
|
167
|
+
- **Third-party scanner not found**: `semgrep` or `snyk` must be installed and on `PATH`.
|
|
168
|
+
- **Upload failures**: The CLI retries 3 times per file. Check file paths and permissions.
|
|
@@ -19,7 +19,7 @@ pub fn run(
|
|
|
19
19
|
println!();
|
|
20
20
|
if *issues {
|
|
21
21
|
let show_everything = !*summary && !*fix_explanation && !*fix_diff;
|
|
22
|
-
let issue_details = match utils::api::get_issue(&config.get_url(),
|
|
22
|
+
let issue_details = match utils::api::get_issue(&config.get_url(), id) {
|
|
23
23
|
Ok(issue) => issue,
|
|
24
24
|
Err(e) => {
|
|
25
25
|
eprintln!("Failed to fetch issue details for issue ID {} with error:\n{}", id, e);
|
|
@@ -69,7 +69,7 @@ pub fn run(
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
} else {
|
|
72
|
-
let scan_details = match utils::api::get_scan(&config.get_url(),
|
|
72
|
+
let scan_details = match utils::api::get_scan(&config.get_url(), id) {
|
|
73
73
|
Ok(details) => details,
|
|
74
74
|
Err(e) => {
|
|
75
75
|
eprintln!("Failed to fetch scan details for scan ID {}: {}", id, e);
|
|
@@ -92,7 +92,7 @@ pub fn run(
|
|
|
92
92
|
print_section("Engine", &scan_details.engine);
|
|
93
93
|
let created_at = chrono::DateTime::<chrono::Utc>::from(SystemTime::now()).format("%Y-%m-%d %H:%M:%S").to_string();
|
|
94
94
|
print_section("Created At", &created_at);
|
|
95
|
-
match scanners::blast::fetch_and_group_scan_issues(&config.get_url(), &
|
|
95
|
+
match scanners::blast::fetch_and_group_scan_issues(&config.get_url(), &scan_details.project) {
|
|
96
96
|
Ok(counts) => {
|
|
97
97
|
let total_issues = counts.values().sum::<usize>();
|
|
98
98
|
let order = vec!["CR", "HI", "ME", "LO"];
|
|
@@ -8,7 +8,7 @@ pub fn run(config: &Config, issues: &bool, sca_issues: &bool, json: &bool, page:
|
|
|
8
8
|
let project_name = utils::generic::get_current_working_directory().unwrap_or("unknown".to_string());
|
|
9
9
|
println!("");
|
|
10
10
|
if *sca_issues {
|
|
11
|
-
let sca_issues_response = match utils::api::get_sca_issues(&config.get_url(),
|
|
11
|
+
let sca_issues_response = match utils::api::get_sca_issues(&config.get_url(), Some((*page).unwrap_or(1)), *page_size, scan_id.clone()) {
|
|
12
12
|
Ok(response) => response,
|
|
13
13
|
Err(e) => {
|
|
14
14
|
debug(&format!("Error Sending Request: {}", e.to_string()));
|
|
@@ -87,7 +87,7 @@ pub fn run(config: &Config, issues: &bool, sca_issues: &bool, json: &bool, page:
|
|
|
87
87
|
|
|
88
88
|
utils::terminal::print_table(table, Some(sca_issues_response.page), Some(sca_issues_response.total_pages));
|
|
89
89
|
} else if *issues {
|
|
90
|
-
let issues_response = match utils::api::get_scan_issues(&config.get_url(), &
|
|
90
|
+
let issues_response = match utils::api::get_scan_issues(&config.get_url(), &project_name, Some((*page).unwrap_or(1)), *page_size, scan_id.clone()) {
|
|
91
91
|
Ok(response) => response,
|
|
92
92
|
Err(e) => {
|
|
93
93
|
debug(&format!("Error Sending Request: {}", e.to_string()));
|
|
@@ -115,7 +115,7 @@ pub fn run(config: &Config, issues: &bool, sca_issues: &bool, json: &bool, page:
|
|
|
115
115
|
if scan_id.is_some() {
|
|
116
116
|
let mut page: u32 = 1;
|
|
117
117
|
loop {
|
|
118
|
-
match utils::api::check_blocking_rules(&config.get_url(),
|
|
118
|
+
match utils::api::check_blocking_rules(&config.get_url(), scan_id.as_ref().unwrap(), Some(page)) {
|
|
119
119
|
Ok(rules) => {
|
|
120
120
|
if rules.block {
|
|
121
121
|
render_blocking_rules = true;
|
|
@@ -224,7 +224,7 @@ pub fn run(config: &Config, issues: &bool, sca_issues: &bool, json: &bool, page:
|
|
|
224
224
|
|
|
225
225
|
utils::terminal::print_table(table, issues_response.page, issues_response.total_pages);
|
|
226
226
|
} else {
|
|
227
|
-
let (scans, page, total_pages) = match utils::api::query_scan_list(&config.get_url(),
|
|
227
|
+
let (scans, page, total_pages) = match utils::api::query_scan_list(&config.get_url(), Some(&project_name), *page, *page_size) {
|
|
228
228
|
Ok(scans) => {
|
|
229
229
|
let page = scans.page;
|
|
230
230
|
let total_pages = scans.total_pages;
|
|
@@ -186,7 +186,8 @@ fn main() {
|
|
|
186
186
|
eprintln!("No token set.\nPlease run 'corgea login' to authenticate.\nFor more info checkout our docs at Check out our docs at https://docs.corgea.app/install_cli#login-with-the-cli");
|
|
187
187
|
std::process::exit(1);
|
|
188
188
|
}
|
|
189
|
-
|
|
189
|
+
utils::api::set_auth_token(&config.get_token());
|
|
190
|
+
match utils::api::verify_token(config.get_url().as_str()) {
|
|
190
191
|
Ok(true) => {
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
@@ -207,7 +208,8 @@ fn main() {
|
|
|
207
208
|
match effective_token {
|
|
208
209
|
Some(token_value) => {
|
|
209
210
|
let token_source = if token.is_some() { "parameter" } else { "CORGEA_TOKEN environment variable" };
|
|
210
|
-
|
|
211
|
+
utils::api::set_auth_token(&token_value);
|
|
212
|
+
match utils::api::verify_token(url.as_deref().unwrap_or(corgea_config.get_url().as_str())) {
|
|
211
213
|
Ok(true) => {
|
|
212
214
|
corgea_config.set_token(token_value.clone()).expect("Failed to set token");
|
|
213
215
|
if let Some(url) = url {
|
|
@@ -131,8 +131,8 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
131
131
|
let github_env_vars = get_github_env_vars();
|
|
132
132
|
|
|
133
133
|
let run_id = Uuid::new_v4().to_string();
|
|
134
|
-
let token = config.get_token();
|
|
135
134
|
let base_url = config.get_url();
|
|
135
|
+
let api_base = "/api/v1";
|
|
136
136
|
let project;
|
|
137
137
|
|
|
138
138
|
if in_ci {
|
|
@@ -143,20 +143,20 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
143
143
|
} else {
|
|
144
144
|
project = utils::generic::determine_project_name(project_name.as_deref());
|
|
145
145
|
}
|
|
146
|
-
let repo_data = std::env::var("REPO_DATA").unwrap_or_else(|_| "".to_string());
|
|
146
|
+
let repo_data = std::env::var("REPO_DATA").unwrap_or_else(|_| "".to_string());
|
|
147
147
|
|
|
148
148
|
let scan_upload_url = if repo_data.is_empty() {
|
|
149
149
|
format!(
|
|
150
|
-
"{}/
|
|
150
|
+
"{}{}/scan-upload?engine={}&run_id={}&project={}&ci={}&ci_platform={}", base_url, api_base, scanner, run_id, project, in_ci, ci_platform
|
|
151
151
|
)
|
|
152
152
|
} else {
|
|
153
153
|
format!(
|
|
154
|
-
"{}/
|
|
154
|
+
"{}{}/scan-upload?engine={}&run_id={}&project={}&ci={}&ci_platform={}&repo_data={}", base_url, api_base, scanner, run_id, project, in_ci, ci_platform, repo_data
|
|
155
155
|
)
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
let git_config_upload_url = format!(
|
|
159
|
-
"{}/
|
|
159
|
+
"{}{}/git-config-upload?run_id={}", base_url, api_base, run_id
|
|
160
160
|
);
|
|
161
161
|
let client = utils::api::http_client();
|
|
162
162
|
|
|
@@ -177,7 +177,7 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
let src_upload_url = format!(
|
|
180
|
-
"{}/
|
|
180
|
+
"{}{}/code-upload?run_id={}&path={}", base_url, api_base, run_id, path
|
|
181
181
|
);
|
|
182
182
|
debug(&format!("Uploading file: {}", path));
|
|
183
183
|
let fp = Path::new(&path);
|
|
@@ -198,7 +198,10 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
198
198
|
match res {
|
|
199
199
|
Ok(response) => {
|
|
200
200
|
if !response.status().is_success() {
|
|
201
|
-
|
|
201
|
+
let status = response.status();
|
|
202
|
+
let body = response.text().unwrap_or_else(|_| "Unable to read response body".to_string());
|
|
203
|
+
debug(&format!("Code upload failed with status: {}. Response body: {}", status, body));
|
|
204
|
+
eprintln!("Failed to upload file {} {}... retrying", status, path);
|
|
202
205
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
203
206
|
attempts += 1;
|
|
204
207
|
} else {
|
|
@@ -231,8 +234,23 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
231
234
|
let input_bytes = input.as_bytes();
|
|
232
235
|
let input_size = input_bytes.len();
|
|
233
236
|
let max_upload_size = 50 * 1024 * 1024; // 50mb
|
|
234
|
-
let chunk_size =
|
|
235
|
-
|
|
237
|
+
let chunk_size = match std::env::var("DEBUG_CORGEA_OVERRIDE_REPORT_CHUNK_SIZE") {
|
|
238
|
+
Ok(val) => {
|
|
239
|
+
match val.parse::<usize>() {
|
|
240
|
+
Ok(mb) if mb > 0 => {
|
|
241
|
+
debug(&format!("Overriding report chunk size to {} MB", mb));
|
|
242
|
+
mb * 1024 * 1024
|
|
243
|
+
}
|
|
244
|
+
_ => {
|
|
245
|
+
eprintln!("Invalid DEBUG_CORGEA_OVERRIDE_REPORT_CHUNK_SIZE value '{}', using default 1 MB", val);
|
|
246
|
+
1024 * 1024
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
Err(_) => 1024 * 1024, // default 1mb
|
|
251
|
+
};
|
|
252
|
+
let is_chunked = input_size > max_upload_size;
|
|
253
|
+
let res = if is_chunked {
|
|
236
254
|
let total_chunks = (input_size + chunk_size - 1) / chunk_size;
|
|
237
255
|
debug(&format!("Uploading scan in {} chunks", total_chunks));
|
|
238
256
|
let mut offset = 0usize;
|
|
@@ -246,8 +264,38 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
246
264
|
.header("Upload-Length", input_size.to_string())
|
|
247
265
|
.body(chunk.to_vec())
|
|
248
266
|
.send();
|
|
267
|
+
|
|
249
268
|
let should_break = match &response {
|
|
250
|
-
Ok(res) =>
|
|
269
|
+
Ok(res) => {
|
|
270
|
+
if !res.status().is_success() {
|
|
271
|
+
true
|
|
272
|
+
} else {
|
|
273
|
+
if let Some(server_offset) = res.headers().get("Upload-Offset") {
|
|
274
|
+
let expected_offset = offset + chunk.len();
|
|
275
|
+
if let Ok(server_offset_str) = server_offset.to_str() {
|
|
276
|
+
if let Ok(server_offset_val) = server_offset_str.parse::<usize>() {
|
|
277
|
+
if server_offset_val != expected_offset {
|
|
278
|
+
eprintln!(
|
|
279
|
+
"Upload offset mismatch on chunk {}/{}: server has {} bytes but expected {}. \
|
|
280
|
+
This may indicate that chunks are being routed to different server instances. \
|
|
281
|
+
Please contact support.",
|
|
282
|
+
index + 1, total_chunks, server_offset_val, expected_offset
|
|
283
|
+
);
|
|
284
|
+
true
|
|
285
|
+
} else {
|
|
286
|
+
false
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
false
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
false
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
false
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
251
299
|
Err(_) => true,
|
|
252
300
|
};
|
|
253
301
|
last_response = Some(response);
|
|
@@ -269,6 +317,8 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
269
317
|
let mut sast_scan_id: Option<String> = None;
|
|
270
318
|
let mut project_id: Option<String> = None;
|
|
271
319
|
|
|
320
|
+
let mut upload_failed = false;
|
|
321
|
+
|
|
272
322
|
match res {
|
|
273
323
|
Ok(response) => {
|
|
274
324
|
if response.status().is_success() {
|
|
@@ -305,13 +355,24 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
305
355
|
}
|
|
306
356
|
}
|
|
307
357
|
}
|
|
308
|
-
|
|
358
|
+
|
|
359
|
+
if is_chunked && sast_scan_id.is_none() {
|
|
360
|
+
eprintln!("Failed to upload scan: server did not return a scan ID after all chunks were sent. The scan was not created on the platform.");
|
|
361
|
+
upload_failed = true;
|
|
362
|
+
} else {
|
|
363
|
+
println!("Successfully uploaded scan.");
|
|
364
|
+
}
|
|
309
365
|
} else {
|
|
310
|
-
|
|
366
|
+
upload_failed = true;
|
|
367
|
+
let status = response.status();
|
|
368
|
+
let body = response.text().unwrap_or_else(|_| "Unable to read response body".to_string());
|
|
369
|
+
debug(&format!("Scan upload failed with status: {}. Response body: {}", status, body));
|
|
370
|
+
eprintln!("Failed to upload scan: {}", status);
|
|
311
371
|
}
|
|
312
372
|
}
|
|
313
373
|
Err(e) => {
|
|
314
374
|
eprintln!("Failed to send request: {}", e);
|
|
375
|
+
upload_failed = true;
|
|
315
376
|
}
|
|
316
377
|
}
|
|
317
378
|
|
|
@@ -343,7 +404,7 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
343
404
|
|
|
344
405
|
if in_ci {
|
|
345
406
|
let ci_data_upload_url = format!(
|
|
346
|
-
"{}/
|
|
407
|
+
"{}{}/ci-data-upload?run_id={}&platform={}", base_url, api_base, run_id, ci_platform
|
|
347
408
|
);
|
|
348
409
|
|
|
349
410
|
let mut github_env_vars_json = serde_json::Map::new();
|
|
@@ -376,6 +437,10 @@ pub fn upload_scan(config: &Config, paths: Vec<String>, scanner: String, input:
|
|
|
376
437
|
}
|
|
377
438
|
}
|
|
378
439
|
|
|
440
|
+
if upload_failed {
|
|
441
|
+
std::process::exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
379
444
|
println!("Successfully scanned using {} and uploaded to Corgea.", scanner);
|
|
380
445
|
|
|
381
446
|
if upload_error_count > 0 {
|
|
@@ -180,7 +180,7 @@ pub fn run(
|
|
|
180
180
|
let _ = packaging_thread.join();
|
|
181
181
|
print!("\r{}Project packaged successfully.\n", utils::terminal::set_text_color("", utils::terminal::TerminalColor::Green));
|
|
182
182
|
println!("\n\nSubmitting scan to Corgea:");
|
|
183
|
-
let upload_result = match utils::api::upload_zip(&zip_path, &config.
|
|
183
|
+
let upload_result = match utils::api::upload_zip(&zip_path, &config.get_url(), &project_name, repo_info, scan_type, policy) {
|
|
184
184
|
Ok(result) => result,
|
|
185
185
|
Err(e) => {
|
|
186
186
|
eprintln!("\n\nOh no! We encountered an issue while uploading the zip file '{}' to the server.\nPlease ensure that:
|
|
@@ -225,7 +225,7 @@ pub fn run(
|
|
|
225
225
|
utils::terminal::show_loading_message("Collecting scan results... ([T]s)", stop_signal_clone);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
let classifications = match report_scan_status(&config.get_url(), &
|
|
228
|
+
let classifications = match report_scan_status(&config.get_url(), &project_name) {
|
|
229
229
|
Ok(issues_classes) => {
|
|
230
230
|
*stop_signal.lock().unwrap() = true;
|
|
231
231
|
let _ = results_thread.join();
|
|
@@ -258,7 +258,7 @@ pub fn run(
|
|
|
258
258
|
}
|
|
259
259
|
};
|
|
260
260
|
if *fail {
|
|
261
|
-
let blocking_rules = match utils::api::check_blocking_rules(&config.get_url(), &
|
|
261
|
+
let blocking_rules = match utils::api::check_blocking_rules(&config.get_url(), &scan_id, None) {
|
|
262
262
|
Ok(rules) => rules,
|
|
263
263
|
Err(e) => {
|
|
264
264
|
eprintln!("Failed to check blocking rules: {}", e);
|
|
@@ -286,14 +286,14 @@ pub fn run(
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
if out_format == "json" {
|
|
289
|
-
let issues = match utils::api::get_all_issues(&config.get_url(), &
|
|
289
|
+
let issues = match utils::api::get_all_issues(&config.get_url(), &project_name, Some(scan_id.clone())) {
|
|
290
290
|
Ok(issues) => issues,
|
|
291
291
|
Err(e) => {
|
|
292
292
|
eprintln!("\n\nFailed to fetch issues: {}\n\n", e);
|
|
293
293
|
std::process::exit(1);
|
|
294
294
|
}
|
|
295
295
|
};
|
|
296
|
-
let sca_issues = match utils::api::get_all_sca_issues(&config.get_url(), &
|
|
296
|
+
let sca_issues = match utils::api::get_all_sca_issues(&config.get_url(), &project_name, Some(scan_id.clone())) {
|
|
297
297
|
Ok(issues) => issues,
|
|
298
298
|
Err(e) => {
|
|
299
299
|
eprintln!("\n\nFailed to fetch SCA issues: {}\n\n", e);
|
|
@@ -311,7 +311,7 @@ pub fn run(
|
|
|
311
311
|
println!("\n\nScan results written to: {}\n\n", out_file.clone());
|
|
312
312
|
}
|
|
313
313
|
else if out_format == "html" {
|
|
314
|
-
let report = match utils::api::get_scan_report(&config.get_url(), &
|
|
314
|
+
let report = match utils::api::get_scan_report(&config.get_url(), &scan_id, None) {
|
|
315
315
|
Ok(html) => html,
|
|
316
316
|
Err(e) => {
|
|
317
317
|
eprintln!("\n\nFailed to fetch scan report: {}\n\n", e);
|
|
@@ -325,7 +325,7 @@ pub fn run(
|
|
|
325
325
|
println!("\n\nScan report written to: {}\n\n", out_file.clone());
|
|
326
326
|
}
|
|
327
327
|
else if out_format == "sarif" {
|
|
328
|
-
let report = match utils::api::get_scan_report(&config.get_url(), &
|
|
328
|
+
let report = match utils::api::get_scan_report(&config.get_url(), &scan_id, Some("sarif")) {
|
|
329
329
|
Ok(sarif) => sarif,
|
|
330
330
|
Err(e) => {
|
|
331
331
|
eprintln!("\n\nFailed to fetch SARIF report: {}\n\n", e);
|
|
@@ -339,7 +339,7 @@ pub fn run(
|
|
|
339
339
|
println!("\n\nScan report written to: {}\n\n", out_file.clone());
|
|
340
340
|
}
|
|
341
341
|
else if out_format == "markdown" {
|
|
342
|
-
let report = match utils::api::get_scan_report(&config.get_url(), &
|
|
342
|
+
let report = match utils::api::get_scan_report(&config.get_url(), &scan_id, Some("markdown")) {
|
|
343
343
|
Ok(markdown) => markdown,
|
|
344
344
|
Err(e) => {
|
|
345
345
|
eprintln!("\n\nFailed to fetch Markdown report: {}\n\n", e);
|
|
@@ -402,7 +402,7 @@ pub fn wait_for_scan(config: &Config, scan_id: &str) {
|
|
|
402
402
|
|
|
403
403
|
loop {
|
|
404
404
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
405
|
-
match check_scan_status(&scan_id, &config.get_url()
|
|
405
|
+
match check_scan_status(&scan_id, &config.get_url()) {
|
|
406
406
|
Ok(true) => {
|
|
407
407
|
*stop_signal.lock().unwrap() = true;
|
|
408
408
|
break;
|
|
@@ -444,16 +444,16 @@ pub fn wait_for_scan(config: &Config, scan_id: &str) {
|
|
|
444
444
|
}
|
|
445
445
|
|
|
446
446
|
|
|
447
|
-
pub fn check_scan_status(scan_id: &str, url: &str
|
|
448
|
-
match utils::api::get_scan(url,
|
|
447
|
+
pub fn check_scan_status(scan_id: &str, url: &str) -> Result<bool, Box<dyn Error>> {
|
|
448
|
+
match utils::api::get_scan(url, scan_id) {
|
|
449
449
|
Ok(scan) => Ok(scan.status == "complete"),
|
|
450
450
|
Err(e) => Err(e)
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
|
|
455
|
-
pub fn fetch_and_group_scan_issues(url: &str,
|
|
456
|
-
let issues = match utils::api::get_all_issues(url,
|
|
455
|
+
pub fn fetch_and_group_scan_issues(url: &str, project: &str) -> Result<HashMap<String, usize>, Box<dyn std::error::Error>> {
|
|
456
|
+
let issues = match utils::api::get_all_issues(url, project, None) {
|
|
457
457
|
Ok(issues) => issues,
|
|
458
458
|
Err(err) => {
|
|
459
459
|
return Err(format!("Failed to fetch scan issues: {}", err).into());
|
|
@@ -468,8 +468,8 @@ pub fn fetch_and_group_scan_issues(url: &str, token: &str, project: &str) -> Res
|
|
|
468
468
|
Ok(classification_counts)
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
-
pub fn report_scan_status(url: &str,
|
|
472
|
-
let classification_counts = match fetch_and_group_scan_issues(url,
|
|
471
|
+
pub fn report_scan_status(url: &str, project: &str) -> Result<HashMap<String, usize>, Box<dyn std::error::Error>>{
|
|
472
|
+
let classification_counts = match fetch_and_group_scan_issues(url, project) {
|
|
473
473
|
Ok(counts) => counts,
|
|
474
474
|
Err(e) => {
|
|
475
475
|
return Err(e);
|
|
@@ -19,20 +19,129 @@ fn get_source() -> String {
|
|
|
19
19
|
std::env::var("CORGEA_SOURCE").unwrap_or_else(|_| "cli".to_string())
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
let
|
|
24
|
-
|
|
22
|
+
fn is_jwt(token: &str) -> bool {
|
|
23
|
+
let parts: Vec<&str> = token.splitn(4, '.').collect();
|
|
24
|
+
parts.len() == 3 && parts.iter().all(|p| !p.is_empty())
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn auth_headers(token: &str) -> HeaderMap {
|
|
28
|
+
let mut headers = HeaderMap::new();
|
|
29
|
+
if is_jwt(token) {
|
|
30
|
+
headers.insert(
|
|
31
|
+
"Authorization",
|
|
32
|
+
format!("Bearer {}", token).parse().unwrap(),
|
|
33
|
+
);
|
|
34
|
+
} else {
|
|
35
|
+
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
36
|
+
}
|
|
37
|
+
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
38
|
+
headers
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static AUTH_TOKEN: std::sync::LazyLock<std::sync::RwLock<String>> =
|
|
42
|
+
std::sync::LazyLock::new(|| std::sync::RwLock::new(String::new()));
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
pub fn set_auth_token(token: &str) {
|
|
45
|
+
*AUTH_TOKEN.write().unwrap() = token.to_string();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static COOKIE_JAR: std::sync::LazyLock<std::sync::Arc<reqwest::cookie::Jar>> =
|
|
49
|
+
std::sync::LazyLock::new(|| std::sync::Arc::new(reqwest::cookie::Jar::default()));
|
|
50
|
+
|
|
51
|
+
static SHARED_CLIENT: std::sync::LazyLock<reqwest::blocking::Client> =
|
|
52
|
+
std::sync::LazyLock::new(|| {
|
|
53
|
+
let mut builder = reqwest::blocking::Client::builder()
|
|
54
|
+
.timeout(std::time::Duration::from_secs(5 * 30))
|
|
55
|
+
.cookie_provider(COOKIE_JAR.clone());
|
|
56
|
+
|
|
57
|
+
if let Ok(https_proxy) = std::env::var("https_proxy") {
|
|
58
|
+
debug(&format!("https_proxy detected: {}", https_proxy));
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
if std::env::var("CORGEA_ACCEPT_CERT").is_ok() {
|
|
61
|
+
debug(&format!("Skipping CA cert validation"));
|
|
62
|
+
builder = builder.danger_accept_invalid_certs(true);
|
|
63
|
+
}
|
|
32
64
|
}
|
|
65
|
+
|
|
66
|
+
builder.build().expect("Failed to build http client")
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
pub struct HttpClient {
|
|
70
|
+
inner: reqwest::blocking::Client,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub struct DebugRequestBuilder {
|
|
74
|
+
client: reqwest::blocking::Client,
|
|
75
|
+
inner: reqwest::blocking::RequestBuilder,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
impl HttpClient {
|
|
79
|
+
pub fn get<U: reqwest::IntoUrl>(&self, url: U) -> DebugRequestBuilder {
|
|
80
|
+
DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.get(url) }
|
|
33
81
|
}
|
|
34
82
|
|
|
35
|
-
|
|
83
|
+
pub fn post<U: reqwest::IntoUrl>(&self, url: U) -> DebugRequestBuilder {
|
|
84
|
+
DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.post(url) }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub fn patch<U: reqwest::IntoUrl>(&self, url: U) -> DebugRequestBuilder {
|
|
88
|
+
DebugRequestBuilder { client: self.inner.clone(), inner: self.inner.patch(url) }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl DebugRequestBuilder {
|
|
93
|
+
pub fn header<K, V>(self, key: K, value: V) -> Self
|
|
94
|
+
where
|
|
95
|
+
reqwest::header::HeaderName: TryFrom<K>,
|
|
96
|
+
<reqwest::header::HeaderName as TryFrom<K>>::Error: Into<http::Error>,
|
|
97
|
+
reqwest::header::HeaderValue: TryFrom<V>,
|
|
98
|
+
<reqwest::header::HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
|
|
99
|
+
{
|
|
100
|
+
Self { inner: self.inner.header(key, value), client: self.client }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn query<T: Serialize + ?Sized>(self, query: &T) -> Self {
|
|
104
|
+
Self { inner: self.inner.query(query), client: self.client }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pub fn multipart(self, form: reqwest::blocking::multipart::Form) -> Self {
|
|
108
|
+
Self { inner: self.inner.multipart(form), client: self.client }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
pub fn body<T: Into<reqwest::blocking::Body>>(self, body: T) -> Self {
|
|
112
|
+
Self { inner: self.inner.body(body), client: self.client }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub fn send(self) -> reqwest::Result<reqwest::blocking::Response> {
|
|
116
|
+
use reqwest::cookie::CookieStore;
|
|
117
|
+
|
|
118
|
+
let token = AUTH_TOKEN.read().unwrap().clone();
|
|
119
|
+
let builder = if !token.is_empty() {
|
|
120
|
+
self.inner.headers(auth_headers(&token))
|
|
121
|
+
} else {
|
|
122
|
+
self.inner
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
let request = builder.build()?;
|
|
126
|
+
|
|
127
|
+
debug(&format!("→ {} {}", request.method(), request.url()));
|
|
128
|
+
debug(&format!(" Request headers: {:?}", request.headers()));
|
|
129
|
+
match COOKIE_JAR.cookies(request.url()) {
|
|
130
|
+
Some(cookies) => debug(&format!(" Cookie: {}", cookies.to_str().unwrap_or("<binary>"))),
|
|
131
|
+
None => debug(" Cookie: (none in jar for this URL)"),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let response = self.client.execute(request)?;
|
|
135
|
+
|
|
136
|
+
debug(&format!("← {} {}", response.status(), response.url()));
|
|
137
|
+
debug(&format!(" Response headers: {:?}", response.headers()));
|
|
138
|
+
|
|
139
|
+
Ok(response)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn http_client() -> HttpClient {
|
|
144
|
+
HttpClient { inner: SHARED_CLIENT.clone() }
|
|
36
145
|
}
|
|
37
146
|
|
|
38
147
|
fn check_for_warnings(headers: &HeaderMap, status: StatusCode) {
|
|
@@ -58,7 +167,6 @@ pub struct UploadZipResult {
|
|
|
58
167
|
|
|
59
168
|
pub fn upload_zip(
|
|
60
169
|
file_path: &str,
|
|
61
|
-
token: &str,
|
|
62
170
|
url: &str,
|
|
63
171
|
project_name: &str,
|
|
64
172
|
repo_info: Option<utils::generic::RepoInfo>,
|
|
@@ -84,8 +192,6 @@ pub fn upload_zip(
|
|
|
84
192
|
|
|
85
193
|
let response_object = client
|
|
86
194
|
.post(format!("{}{}/start-scan", url, API_BASE))
|
|
87
|
-
.header("CORGEA-TOKEN", token)
|
|
88
|
-
.header("CORGEA-SOURCE", get_source())
|
|
89
195
|
.query(&[
|
|
90
196
|
("scan_type", "blast"),
|
|
91
197
|
])
|
|
@@ -174,8 +280,6 @@ pub fn upload_zip(
|
|
|
174
280
|
|
|
175
281
|
let response = match client
|
|
176
282
|
.patch(format!("{}{}/start-scan/{}/", url, API_BASE, transfer_id))
|
|
177
|
-
.header("CORGEA-TOKEN", token)
|
|
178
|
-
.header("CORGEA-SOURCE", get_source())
|
|
179
283
|
.header("Upload-Offset", offset.to_string())
|
|
180
284
|
.header("Upload-Length", file_size.to_string())
|
|
181
285
|
.header("Upload-Name", file_name)
|
|
@@ -236,12 +340,12 @@ pub fn upload_zip(
|
|
|
236
340
|
Err("Failed to upload file".into())
|
|
237
341
|
}
|
|
238
342
|
|
|
239
|
-
pub fn get_all_issues(url: &str,
|
|
343
|
+
pub fn get_all_issues(url: &str, project: &str, scan_id: Option<String>) -> Result<Vec<Issue>, Box<dyn std::error::Error>> {
|
|
240
344
|
let mut all_issues = Vec::new();
|
|
241
345
|
let mut current_page: u32 = 1;
|
|
242
346
|
|
|
243
347
|
loop {
|
|
244
|
-
let response = match get_scan_issues(url,
|
|
348
|
+
let response = match get_scan_issues(url, project, Some(current_page as u16), Some(30), scan_id.clone()) {
|
|
245
349
|
Ok(response) => response,
|
|
246
350
|
Err(e) => return Err(format!("Failed to get scan issues: {}", e).into())
|
|
247
351
|
};
|
|
@@ -267,7 +371,6 @@ pub fn get_all_issues(url: &str, token: &str, project: &str, scan_id: Option<Str
|
|
|
267
371
|
|
|
268
372
|
pub fn get_scan_issues(
|
|
269
373
|
url: &str,
|
|
270
|
-
token: &str,
|
|
271
374
|
project: &str,
|
|
272
375
|
page: Option<u16>,
|
|
273
376
|
page_size: Option<u16>,
|
|
@@ -295,14 +398,10 @@ pub fn get_scan_issues(
|
|
|
295
398
|
url.push_str("&page_size=30");
|
|
296
399
|
}
|
|
297
400
|
let client = http_client();
|
|
298
|
-
let mut headers = HeaderMap::new();
|
|
299
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
300
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
301
401
|
|
|
302
402
|
debug(&format!("Sending request to URL: {}", url));
|
|
303
|
-
debug(&format!("Headers: {:?}", headers));
|
|
304
403
|
|
|
305
|
-
let response = match client.get(&url).
|
|
404
|
+
let response = match client.get(&url).send() {
|
|
306
405
|
Ok(res) => {
|
|
307
406
|
check_for_warnings(res.headers(), res.status());
|
|
308
407
|
res
|
|
@@ -324,19 +423,13 @@ pub fn get_scan_issues(
|
|
|
324
423
|
}
|
|
325
424
|
}
|
|
326
425
|
|
|
327
|
-
pub fn get_scan(url: &str,
|
|
426
|
+
pub fn get_scan(url: &str, scan_id: &str) -> Result<ScanResponse, Box<dyn std::error::Error>> {
|
|
328
427
|
let url = format!("{}{}/scan/{}", url, API_BASE, scan_id);
|
|
329
428
|
|
|
330
429
|
let client = http_client();
|
|
331
|
-
|
|
332
|
-
let mut headers = HeaderMap::new();
|
|
333
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
334
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
335
430
|
debug(&format!("Sending request to URL: {}", url));
|
|
336
|
-
debug(&format!("Headers: {:?}", headers));
|
|
337
431
|
let response = client
|
|
338
432
|
.get(&url)
|
|
339
|
-
.headers(headers)
|
|
340
433
|
.send()
|
|
341
434
|
.map_err(|e| format!("Failed to send request: {}", e))?;
|
|
342
435
|
|
|
@@ -354,7 +447,7 @@ pub fn get_scan(url: &str, token: &str, scan_id: &str) -> Result<ScanResponse, B
|
|
|
354
447
|
}
|
|
355
448
|
}
|
|
356
449
|
|
|
357
|
-
pub fn get_scan_report(url: &str,
|
|
450
|
+
pub fn get_scan_report(url: &str, scan_id: &str, format: Option<&str>) -> Result<String, Box<dyn std::error::Error>> {
|
|
358
451
|
let url = if let Some(fmt) = format {
|
|
359
452
|
format!("{}{}/scan/{}/report?format={}", url, API_BASE, scan_id, fmt)
|
|
360
453
|
} else {
|
|
@@ -362,16 +455,11 @@ pub fn get_scan_report(url: &str, token: &str, scan_id: &str, format: Option<&st
|
|
|
362
455
|
};
|
|
363
456
|
|
|
364
457
|
let client = http_client();
|
|
365
|
-
let mut headers = HeaderMap::new();
|
|
366
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
367
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
368
458
|
|
|
369
459
|
debug(&format!("Sending request to URL: {}", url));
|
|
370
|
-
debug(&format!("Headers: {:?}", headers));
|
|
371
460
|
|
|
372
461
|
let response = client
|
|
373
462
|
.get(&url)
|
|
374
|
-
.headers(headers)
|
|
375
463
|
.send()
|
|
376
464
|
.map_err(|e| format!("Failed to send request: {}", e))?;
|
|
377
465
|
|
|
@@ -384,7 +472,7 @@ pub fn get_scan_report(url: &str, token: &str, scan_id: &str, format: Option<&st
|
|
|
384
472
|
}
|
|
385
473
|
}
|
|
386
474
|
|
|
387
|
-
pub fn get_issue(url: &str,
|
|
475
|
+
pub fn get_issue(url: &str, issue: &str) -> Result<FullIssueResponse, Box<dyn std::error::Error>> {
|
|
388
476
|
let url = format!(
|
|
389
477
|
"{}{}/issue/{}",
|
|
390
478
|
url,
|
|
@@ -392,12 +480,8 @@ pub fn get_issue(url: &str, token: &str, issue: &str) -> Result<FullIssueRespons
|
|
|
392
480
|
issue,
|
|
393
481
|
);
|
|
394
482
|
let client = http_client();
|
|
395
|
-
let mut headers = HeaderMap::new();
|
|
396
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
397
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
398
483
|
debug(&format!("Sending request to URL: {}", url));
|
|
399
|
-
|
|
400
|
-
let response = match client.get(&url).headers(headers).send() {
|
|
484
|
+
let response = match client.get(&url).send() {
|
|
401
485
|
Ok(res) => {
|
|
402
486
|
check_for_warnings(res.headers(), res.status());
|
|
403
487
|
res
|
|
@@ -418,7 +502,6 @@ pub fn get_issue(url: &str, token: &str, issue: &str) -> Result<FullIssueRespons
|
|
|
418
502
|
|
|
419
503
|
pub fn query_scan_list(
|
|
420
504
|
url: &str,
|
|
421
|
-
token: &str,
|
|
422
505
|
project: Option<&str>,
|
|
423
506
|
page: Option<u16>,
|
|
424
507
|
page_size: Option<u16>
|
|
@@ -437,14 +520,9 @@ pub fn query_scan_list(
|
|
|
437
520
|
|
|
438
521
|
|
|
439
522
|
let client = http_client();
|
|
440
|
-
let mut headers = HeaderMap::new();
|
|
441
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
442
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
443
523
|
debug(&format!("Sending request to URL: {}", url));
|
|
444
|
-
debug(&format!("Headers: {:?}", headers));
|
|
445
524
|
let response = match client
|
|
446
525
|
.get(url)
|
|
447
|
-
.headers(headers)
|
|
448
526
|
.query(&query_params)
|
|
449
527
|
.send() {
|
|
450
528
|
Ok(res) => {
|
|
@@ -498,18 +576,13 @@ pub fn exchange_code_for_token(
|
|
|
498
576
|
}
|
|
499
577
|
}
|
|
500
578
|
|
|
501
|
-
pub fn verify_token(
|
|
579
|
+
pub fn verify_token(corgea_url: &str) -> Result<bool, Box<dyn Error>> {
|
|
502
580
|
let url = format!("{}{}/verify", corgea_url, API_BASE);
|
|
503
581
|
let client = http_client();
|
|
504
|
-
let mut headers = HeaderMap::new();
|
|
505
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
506
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
507
582
|
debug(&format!("Sending request to URL: {}", url));
|
|
508
|
-
debug(&format!("Headers: {:?}", headers));
|
|
509
583
|
|
|
510
584
|
let response = client
|
|
511
585
|
.get(&url)
|
|
512
|
-
.headers(headers)
|
|
513
586
|
.send()?;
|
|
514
587
|
|
|
515
588
|
check_for_warnings(response.headers(), response.status());
|
|
@@ -532,7 +605,6 @@ pub fn verify_token(token: &str, corgea_url: &str) -> Result<bool, Box<dyn Error
|
|
|
532
605
|
|
|
533
606
|
pub fn check_blocking_rules(
|
|
534
607
|
url: &str,
|
|
535
|
-
token: &str,
|
|
536
608
|
sast_scan_id: &str,
|
|
537
609
|
page: Option<u32>
|
|
538
610
|
) -> Result<BlockingRuleResponse, Box<dyn Error>> {
|
|
@@ -541,16 +613,11 @@ pub fn check_blocking_rules(
|
|
|
541
613
|
let query_params = vec![("page", page.to_string())];
|
|
542
614
|
|
|
543
615
|
let client = http_client();
|
|
544
|
-
let mut headers = HeaderMap::new();
|
|
545
|
-
headers.insert("CORGEA-TOKEN", token.parse().unwrap());
|
|
546
|
-
headers.insert("CORGEA-SOURCE", get_source().parse().unwrap());
|
|
547
616
|
debug(&format!("Sending request to URL: {}", url));
|
|
548
|
-
debug(&format!("Headers: {:?}", headers));
|
|
549
617
|
debug(&format!("Query params: {:?}", query_params));
|
|
550
618
|
|
|
551
619
|
let response = match client
|
|
552
620
|
.get(url)
|
|
553
|
-
.headers(headers)
|
|
554
621
|
.query(&query_params)
|
|
555
622
|
.send() {
|
|
556
623
|
Ok(res) => {
|
|
@@ -583,7 +650,6 @@ pub fn check_blocking_rules(
|
|
|
583
650
|
|
|
584
651
|
pub fn get_sca_issues(
|
|
585
652
|
url: &str,
|
|
586
|
-
token: &str,
|
|
587
653
|
page: Option<u16>,
|
|
588
654
|
page_size: Option<u16>,
|
|
589
655
|
scan_id: Option<String>
|
|
@@ -605,12 +671,9 @@ pub fn get_sca_issues(
|
|
|
605
671
|
|
|
606
672
|
debug(&format!("Sending request to URL: {}", endpoint));
|
|
607
673
|
debug(&format!("Query params: {:?}", query_params));
|
|
608
|
-
debug(&format!("Token: {}", token));
|
|
609
674
|
|
|
610
675
|
let response = client
|
|
611
676
|
.get(&endpoint)
|
|
612
|
-
.header("CORGEA-TOKEN", token)
|
|
613
|
-
.header("CORGEA-SOURCE", get_source())
|
|
614
677
|
.query(&query_params)
|
|
615
678
|
.send();
|
|
616
679
|
|
|
@@ -646,7 +709,6 @@ pub fn get_sca_issues(
|
|
|
646
709
|
|
|
647
710
|
pub fn get_all_sca_issues(
|
|
648
711
|
url: &str,
|
|
649
|
-
token: &str,
|
|
650
712
|
_project: &str,
|
|
651
713
|
scan_id: Option<String>
|
|
652
714
|
) -> Result<Vec<SCAIssue>, Box<dyn std::error::Error>> {
|
|
@@ -654,7 +716,7 @@ pub fn get_all_sca_issues(
|
|
|
654
716
|
let mut current_page: u32 = 1;
|
|
655
717
|
|
|
656
718
|
loop {
|
|
657
|
-
let response = match get_sca_issues(url,
|
|
719
|
+
let response = match get_sca_issues(url, Some(current_page as u16), Some(30), scan_id.clone()) {
|
|
658
720
|
Ok(response) => response,
|
|
659
721
|
Err(e) => return Err(format!("Failed to get SCA issues: {}", e).into())
|
|
660
722
|
};
|
|
@@ -12,7 +12,7 @@ pub fn run(config: &Config, scan_id: Option<String>, project_id: Option<String>)
|
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
let scans_result = utils::api::query_scan_list(&config.get_url(),
|
|
15
|
+
let scans_result = utils::api::query_scan_list(&config.get_url(), Some(&project_name), Some(1), None);
|
|
16
16
|
let scans: Vec<utils::api::ScanResponse> = match scans_result {
|
|
17
17
|
Ok(result) => result.scans.unwrap_or_default(),
|
|
18
18
|
Err(e) => {
|
|
@@ -31,7 +31,7 @@ pub fn run(config: &Config, scan_id: Option<String>, project_id: Option<String>)
|
|
|
31
31
|
};
|
|
32
32
|
let (scan_id, processed) = match scan_id {
|
|
33
33
|
Some(scan_id) => {
|
|
34
|
-
let processed = match blast::check_scan_status(&scan_id, &config.get_url()
|
|
34
|
+
let processed = match blast::check_scan_status(&scan_id, &config.get_url()) {
|
|
35
35
|
Ok(processed) => processed,
|
|
36
36
|
Err(_) => {
|
|
37
37
|
eprintln!(
|
|
@@ -73,7 +73,7 @@ pub fn run(config: &Config, scan_id: Option<String>, project_id: Option<String>)
|
|
|
73
73
|
print!("Scan has been processed successfully!\n");
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
match blast::report_scan_status(&config.get_url(), &
|
|
76
|
+
match blast::report_scan_status(&config.get_url(), &project_name) {
|
|
77
77
|
Ok(_) => {
|
|
78
78
|
println!(
|
|
79
79
|
"\n\nYou can view the scan results at the following link:\n{}",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|