prismor 0.1.2__py3-none-any.whl → 1.0.5__py3-none-any.whl
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.
- prismor/__init__.py +1 -1
- prismor/api.py +262 -17
- prismor/cli.py +300 -40
- {prismor-0.1.2.dist-info → prismor-1.0.5.dist-info}/METADATA +189 -32
- prismor-1.0.5.dist-info/RECORD +9 -0
- prismor-0.1.2.dist-info/RECORD +0 -9
- {prismor-0.1.2.dist-info → prismor-1.0.5.dist-info}/WHEEL +0 -0
- {prismor-0.1.2.dist-info → prismor-1.0.5.dist-info}/entry_points.txt +0 -0
- {prismor-0.1.2.dist-info → prismor-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {prismor-0.1.2.dist-info → prismor-1.0.5.dist-info}/top_level.txt +0 -0
prismor/__init__.py
CHANGED
prismor/api.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""API client for Prismor security scanning service."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import re
|
|
4
5
|
import requests
|
|
5
6
|
from typing import Optional, Dict, Any
|
|
7
|
+
from urllib.parse import urlparse
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
class PrismorAPIError(Exception):
|
|
@@ -10,6 +12,137 @@ class PrismorAPIError(Exception):
|
|
|
10
12
|
pass
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def parse_github_repo(repo_input: str) -> str:
|
|
16
|
+
"""Extract user/repo_name from various GitHub URL formats or return as-is if already in correct format.
|
|
17
|
+
|
|
18
|
+
This function handles multiple GitHub URL formats:
|
|
19
|
+
- user/repo_name (already in correct format)
|
|
20
|
+
- https://github.com/user/repo_name
|
|
21
|
+
- https://www.github.com/user/repo_name
|
|
22
|
+
- http://github.com/user/repo_name
|
|
23
|
+
- http://www.github.com/user/repo_name
|
|
24
|
+
- github.com/user/repo_name
|
|
25
|
+
- www.github.com/user/repo_name
|
|
26
|
+
- git@github.com:user/repo_name.git
|
|
27
|
+
- https://github.com/user/repo_name.git
|
|
28
|
+
- https://github.com/user/repo_name/
|
|
29
|
+
- https://github.com/user/repo_name#branch
|
|
30
|
+
- https://github.com/user/repo_name/tree/branch
|
|
31
|
+
- https://github.com/user/repo_name/blob/branch/file
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
repo_input: Repository input in any of the supported formats
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Repository in "user/repo_name" format
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
PrismorAPIError: If the input format is not recognized or invalid
|
|
41
|
+
"""
|
|
42
|
+
if not repo_input or not isinstance(repo_input, str):
|
|
43
|
+
raise PrismorAPIError("Repository input cannot be empty")
|
|
44
|
+
|
|
45
|
+
repo_input = repo_input.strip()
|
|
46
|
+
|
|
47
|
+
# If it's already in user/repo format (no protocol, no domain), return as-is
|
|
48
|
+
if "/" in repo_input and not any(repo_input.startswith(prefix) for prefix in
|
|
49
|
+
["http://", "https://", "git@", "github.com", "www.github.com"]):
|
|
50
|
+
# Validate it has exactly one slash and both parts are non-empty
|
|
51
|
+
parts = repo_input.split("/")
|
|
52
|
+
if len(parts) == 2 and parts[0] and parts[1]:
|
|
53
|
+
return repo_input
|
|
54
|
+
else:
|
|
55
|
+
raise PrismorAPIError(f"Invalid repository format: {repo_input}. Expected 'user/repo_name'")
|
|
56
|
+
|
|
57
|
+
# Handle SSH format: git@github.com:user/repo.git
|
|
58
|
+
if repo_input.startswith("git@github.com:"):
|
|
59
|
+
repo_part = repo_input[15:] # Remove "git@github.com:"
|
|
60
|
+
# Remove .git suffix if present
|
|
61
|
+
if repo_part.endswith(".git"):
|
|
62
|
+
repo_part = repo_part[:-4]
|
|
63
|
+
if "/" in repo_part:
|
|
64
|
+
return repo_part
|
|
65
|
+
else:
|
|
66
|
+
raise PrismorAPIError(f"Invalid SSH repository format: {repo_input}")
|
|
67
|
+
|
|
68
|
+
# Handle HTTP/HTTPS URLs
|
|
69
|
+
if repo_input.startswith(("http://", "https://")):
|
|
70
|
+
try:
|
|
71
|
+
parsed = urlparse(repo_input)
|
|
72
|
+
hostname = parsed.hostname.lower()
|
|
73
|
+
|
|
74
|
+
# Check if it's a GitHub URL
|
|
75
|
+
if hostname in ["github.com", "www.github.com"]:
|
|
76
|
+
path = parsed.path.strip("/")
|
|
77
|
+
|
|
78
|
+
# Remove .git suffix if present
|
|
79
|
+
if path.endswith(".git"):
|
|
80
|
+
path = path[:-4]
|
|
81
|
+
|
|
82
|
+
# Split path and extract user/repo
|
|
83
|
+
path_parts = path.split("/")
|
|
84
|
+
if len(path_parts) >= 2:
|
|
85
|
+
user = path_parts[0]
|
|
86
|
+
repo = path_parts[1]
|
|
87
|
+
|
|
88
|
+
# Handle special GitHub paths like /tree/branch, /blob/branch/file
|
|
89
|
+
if len(path_parts) > 2 and path_parts[2] in ["tree", "blob"]:
|
|
90
|
+
# This is a branch/file reference, just take user/repo
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
if user and repo:
|
|
94
|
+
return f"{user}/{repo}"
|
|
95
|
+
else:
|
|
96
|
+
raise PrismorAPIError(f"Invalid GitHub URL format: {repo_input}")
|
|
97
|
+
else:
|
|
98
|
+
raise PrismorAPIError(f"Invalid GitHub URL format: {repo_input}")
|
|
99
|
+
else:
|
|
100
|
+
raise PrismorAPIError(f"Not a GitHub URL: {repo_input}")
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise PrismorAPIError(f"Failed to parse URL: {repo_input}. Error: {str(e)}")
|
|
104
|
+
|
|
105
|
+
# Handle bare domain formats: github.com/user/repo or www.github.com/user/repo
|
|
106
|
+
if repo_input.startswith(("github.com/", "www.github.com/")):
|
|
107
|
+
# Remove domain prefix
|
|
108
|
+
if repo_input.startswith("github.com/"):
|
|
109
|
+
repo_part = repo_input[11:] # Remove "github.com/"
|
|
110
|
+
else: # www.github.com/
|
|
111
|
+
repo_part = repo_input[15:] # Remove "www.github.com/"
|
|
112
|
+
|
|
113
|
+
# Remove .git suffix if present
|
|
114
|
+
if repo_part.endswith(".git"):
|
|
115
|
+
repo_part = repo_part[:-4]
|
|
116
|
+
|
|
117
|
+
# Remove trailing slash
|
|
118
|
+
repo_part = repo_part.rstrip("/")
|
|
119
|
+
|
|
120
|
+
# Split and validate
|
|
121
|
+
parts = repo_part.split("/")
|
|
122
|
+
if len(parts) >= 2:
|
|
123
|
+
user = parts[0]
|
|
124
|
+
repo = parts[1]
|
|
125
|
+
|
|
126
|
+
# Handle special GitHub paths like /tree/branch, /blob/branch/file
|
|
127
|
+
if len(parts) > 2 and parts[2] in ["tree", "blob"]:
|
|
128
|
+
# This is a branch/file reference, just take user/repo
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
if user and repo:
|
|
132
|
+
return f"{user}/{repo}"
|
|
133
|
+
else:
|
|
134
|
+
raise PrismorAPIError(f"Invalid repository format: {repo_input}")
|
|
135
|
+
else:
|
|
136
|
+
raise PrismorAPIError(f"Invalid repository format: {repo_input}")
|
|
137
|
+
|
|
138
|
+
# If we get here, the format is not recognized
|
|
139
|
+
raise PrismorAPIError(
|
|
140
|
+
f"Unrecognized repository format: {repo_input}. "
|
|
141
|
+
"Supported formats: 'user/repo', 'https://github.com/user/repo', "
|
|
142
|
+
"'git@github.com:user/repo.git', or 'github.com/user/repo'"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
13
146
|
class PrismorClient:
|
|
14
147
|
"""Client for interacting with Prismor API."""
|
|
15
148
|
|
|
@@ -23,7 +156,8 @@ class PrismorClient:
|
|
|
23
156
|
if not self.api_key:
|
|
24
157
|
raise PrismorAPIError(
|
|
25
158
|
"PRISMOR_API_KEY environment variable is not set. "
|
|
26
|
-
"Please
|
|
159
|
+
"Please specify your API key. You can generate one for free at https://www.prismor.dev/cli\n"
|
|
160
|
+
"Set it with: export PRISMOR_API_KEY=your_api_key"
|
|
27
161
|
)
|
|
28
162
|
|
|
29
163
|
# self.base_url = "http://localhost:3000"
|
|
@@ -76,27 +210,21 @@ class PrismorClient:
|
|
|
76
210
|
"""Normalize repository input to a full GitHub URL.
|
|
77
211
|
|
|
78
212
|
Args:
|
|
79
|
-
repo: Repository in
|
|
213
|
+
repo: Repository in various formats (username/repo, GitHub URL, etc.)
|
|
80
214
|
|
|
81
215
|
Returns:
|
|
82
216
|
Full GitHub repository URL
|
|
83
217
|
"""
|
|
84
|
-
|
|
85
|
-
|
|
218
|
+
# Use the comprehensive parser to extract user/repo_name
|
|
219
|
+
repo_name = parse_github_repo(repo)
|
|
86
220
|
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
return f"https://github.com/{repo}"
|
|
90
|
-
|
|
91
|
-
raise PrismorAPIError(
|
|
92
|
-
f"Invalid repository format: {repo}. "
|
|
93
|
-
"Please use 'username/repo' or full GitHub URL"
|
|
94
|
-
)
|
|
221
|
+
# Convert to full GitHub URL
|
|
222
|
+
return f"https://github.com/{repo_name}"
|
|
95
223
|
|
|
96
224
|
def scan(
|
|
97
225
|
self,
|
|
98
226
|
repo: str,
|
|
99
|
-
|
|
227
|
+
scan: bool = False,
|
|
100
228
|
sbom: bool = False,
|
|
101
229
|
detect_secret: bool = False,
|
|
102
230
|
fullscan: bool = False,
|
|
@@ -106,7 +234,7 @@ class PrismorClient:
|
|
|
106
234
|
|
|
107
235
|
Args:
|
|
108
236
|
repo: Repository URL or username/repo format
|
|
109
|
-
|
|
237
|
+
scan: Enable vulnerability scanning
|
|
110
238
|
sbom: Enable SBOM generation
|
|
111
239
|
detect_secret: Enable secret detection
|
|
112
240
|
fullscan: Enable all scan types
|
|
@@ -125,7 +253,7 @@ class PrismorClient:
|
|
|
125
253
|
payload = {
|
|
126
254
|
"repo_url": repo_url,
|
|
127
255
|
"api_key": self.api_key,
|
|
128
|
-
"
|
|
256
|
+
"scan": scan or fullscan,
|
|
129
257
|
"sbom": sbom or fullscan,
|
|
130
258
|
"detect_secret": detect_secret or fullscan,
|
|
131
259
|
"fullscan": fullscan,
|
|
@@ -133,11 +261,13 @@ class PrismorClient:
|
|
|
133
261
|
}
|
|
134
262
|
|
|
135
263
|
try:
|
|
264
|
+
# Note: Vulnerability scans now run asynchronously and can take up to 10 minutes
|
|
265
|
+
# The web API handles polling internally, so we just need a longer timeout
|
|
136
266
|
response = requests.post(
|
|
137
267
|
f"{self.base_url}/api/cli/scan",
|
|
138
268
|
json=payload,
|
|
139
269
|
headers={"Content-Type": "application/json"},
|
|
140
|
-
timeout=
|
|
270
|
+
timeout=600 # 10 minute timeout to accommodate async vulnerability scans
|
|
141
271
|
)
|
|
142
272
|
|
|
143
273
|
if response.status_code == 401:
|
|
@@ -166,8 +296,123 @@ class PrismorClient:
|
|
|
166
296
|
|
|
167
297
|
except requests.exceptions.Timeout:
|
|
168
298
|
raise PrismorAPIError(
|
|
169
|
-
"Request timed out. The repository scan is taking longer than expected."
|
|
299
|
+
"Request timed out. The repository scan is taking longer than expected. "
|
|
300
|
+
"Large repositories may require more time. Please try again or check the dashboard for results."
|
|
301
|
+
)
|
|
302
|
+
except requests.exceptions.ConnectionError:
|
|
303
|
+
raise PrismorAPIError(
|
|
304
|
+
"Failed to connect to Prismor API. Please check your internet connection."
|
|
305
|
+
)
|
|
306
|
+
except requests.exceptions.RequestException as e:
|
|
307
|
+
raise PrismorAPIError(f"Request failed: {str(e)}")
|
|
308
|
+
|
|
309
|
+
def start_vulnerability_scan(
|
|
310
|
+
self,
|
|
311
|
+
repo: str,
|
|
312
|
+
branch: Optional[str] = None,
|
|
313
|
+
github_token: Optional[str] = None
|
|
314
|
+
) -> Dict[str, Any]:
|
|
315
|
+
"""Start a vulnerability scan and return immediately with a job_id.
|
|
316
|
+
|
|
317
|
+
This method directly calls the backend API to start an async scan.
|
|
318
|
+
Use check_scan_status() to poll for completion.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
repo: Repository URL or username/repo format
|
|
322
|
+
branch: Specific branch to scan (defaults to main)
|
|
323
|
+
github_token: Optional GitHub token. If not provided, will try to get from env var GITHUB_TOKEN
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Dictionary containing job_id and status information
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
PrismorAPIError: If request fails
|
|
330
|
+
"""
|
|
331
|
+
# Directly call the backend API
|
|
332
|
+
backend_url = os.environ.get(
|
|
333
|
+
"PRISMOR_BACKEND_URL",
|
|
334
|
+
"https://2dlxuia6i5.execute-api.us-east-1.amazonaws.com/prod"
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
repo_url = self.normalize_repo_url(repo)
|
|
338
|
+
|
|
339
|
+
# Get GitHub token from parameter, env var, or raise error
|
|
340
|
+
gh_token = github_token or os.environ.get("GITHUB_TOKEN")
|
|
341
|
+
|
|
342
|
+
if not gh_token:
|
|
343
|
+
raise PrismorAPIError(
|
|
344
|
+
"GitHub token required. Provide it as a parameter, set GITHUB_TOKEN environment variable, "
|
|
345
|
+
"or use 'prismor --scan <repo> --scan' which handles authentication automatically."
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
response = requests.post(
|
|
350
|
+
f"{backend_url}/scan",
|
|
351
|
+
json={
|
|
352
|
+
"repo_url": repo_url,
|
|
353
|
+
"token": gh_token,
|
|
354
|
+
"branch": branch or "main"
|
|
355
|
+
},
|
|
356
|
+
headers={"Content-Type": "application/json"},
|
|
357
|
+
timeout=30
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if response.status_code >= 400:
|
|
361
|
+
error_msg = response.json().get("error", "Unknown error")
|
|
362
|
+
raise PrismorAPIError(f"API error: {error_msg}")
|
|
363
|
+
|
|
364
|
+
response.raise_for_status()
|
|
365
|
+
return response.json()
|
|
366
|
+
|
|
367
|
+
except requests.exceptions.Timeout:
|
|
368
|
+
raise PrismorAPIError("Request timed out.")
|
|
369
|
+
except requests.exceptions.ConnectionError:
|
|
370
|
+
raise PrismorAPIError(
|
|
371
|
+
"Failed to connect to Prismor API. Please check your internet connection."
|
|
372
|
+
)
|
|
373
|
+
except requests.exceptions.RequestException as e:
|
|
374
|
+
raise PrismorAPIError(f"Request failed: {str(e)}")
|
|
375
|
+
|
|
376
|
+
def check_scan_status(self, job_id: str) -> Dict[str, Any]:
|
|
377
|
+
"""Check the status of a vulnerability scan job.
|
|
378
|
+
|
|
379
|
+
This method directly calls the backend API to check scan status.
|
|
380
|
+
Use this when you have a job_id from starting an async scan.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
job_id: The job ID returned from starting a scan
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Dictionary containing scan status and results if completed
|
|
387
|
+
|
|
388
|
+
Raises:
|
|
389
|
+
PrismorAPIError: If request fails
|
|
390
|
+
"""
|
|
391
|
+
# Directly call the backend API for status check
|
|
392
|
+
backend_url = os.environ.get(
|
|
393
|
+
"PRISMOR_BACKEND_URL",
|
|
394
|
+
"https://2dlxuia6i5.execute-api.us-east-1.amazonaws.com/prod"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
response = requests.get(
|
|
399
|
+
f"{backend_url}/scan/status/{job_id}",
|
|
400
|
+
headers={"Content-Type": "application/json"},
|
|
401
|
+
timeout=30
|
|
170
402
|
)
|
|
403
|
+
|
|
404
|
+
if response.status_code == 404:
|
|
405
|
+
raise PrismorAPIError(f"Scan job '{job_id}' not found.")
|
|
406
|
+
|
|
407
|
+
if response.status_code >= 400:
|
|
408
|
+
error_msg = response.json().get("error", "Unknown error")
|
|
409
|
+
raise PrismorAPIError(f"API error: {error_msg}")
|
|
410
|
+
|
|
411
|
+
response.raise_for_status()
|
|
412
|
+
return response.json()
|
|
413
|
+
|
|
414
|
+
except requests.exceptions.Timeout:
|
|
415
|
+
raise PrismorAPIError("Request timed out.")
|
|
171
416
|
except requests.exceptions.ConnectionError:
|
|
172
417
|
raise PrismorAPIError(
|
|
173
418
|
"Failed to connect to Prismor API. Please check your internet connection."
|
prismor/cli.py
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
import sys
|
|
4
4
|
import json
|
|
5
5
|
import click
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
6
8
|
from typing import Optional
|
|
7
|
-
from .api import PrismorClient, PrismorAPIError
|
|
9
|
+
from .api import PrismorClient, PrismorAPIError, parse_github_repo
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def print_success(message: str):
|
|
@@ -27,6 +29,43 @@ def print_warning(message: str):
|
|
|
27
29
|
click.secho(f"⚠ {message}", fg="yellow")
|
|
28
30
|
|
|
29
31
|
|
|
32
|
+
class Spinner:
|
|
33
|
+
"""Simple spinner for showing loading state."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, message: str = "Processing"):
|
|
36
|
+
self.message = message
|
|
37
|
+
self.spinner_chars = "|/-\\"
|
|
38
|
+
self.spinner_index = 0
|
|
39
|
+
self.running = False
|
|
40
|
+
self.thread = None
|
|
41
|
+
|
|
42
|
+
def _spin(self):
|
|
43
|
+
"""Internal spinner loop."""
|
|
44
|
+
while self.running:
|
|
45
|
+
char = self.spinner_chars[self.spinner_index % len(self.spinner_chars)]
|
|
46
|
+
sys.stdout.write(f"\r{char} {self.message}...")
|
|
47
|
+
sys.stdout.flush()
|
|
48
|
+
self.spinner_index += 1
|
|
49
|
+
time.sleep(0.1)
|
|
50
|
+
|
|
51
|
+
def start(self):
|
|
52
|
+
"""Start the spinner."""
|
|
53
|
+
self.running = True
|
|
54
|
+
self.thread = threading.Thread(target=self._spin, daemon=True)
|
|
55
|
+
self.thread.start()
|
|
56
|
+
|
|
57
|
+
def stop(self, message: str = None):
|
|
58
|
+
"""Stop the spinner and clear the line."""
|
|
59
|
+
self.running = False
|
|
60
|
+
if self.thread:
|
|
61
|
+
self.thread.join(timeout=0.2)
|
|
62
|
+
# Clear the spinner line
|
|
63
|
+
sys.stdout.write("\r" + " " * (len(self.message) + 15) + "\r")
|
|
64
|
+
sys.stdout.flush()
|
|
65
|
+
if message:
|
|
66
|
+
click.echo(message)
|
|
67
|
+
|
|
68
|
+
|
|
30
69
|
def format_scan_results(results: dict, scan_type: str):
|
|
31
70
|
"""Format and display scan results."""
|
|
32
71
|
click.echo("\n" + "=" * 60)
|
|
@@ -117,71 +156,85 @@ def format_scan_results(results: dict, scan_type: str):
|
|
|
117
156
|
|
|
118
157
|
@click.group(invoke_without_command=True)
|
|
119
158
|
@click.option(
|
|
120
|
-
"--
|
|
159
|
+
"--repo",
|
|
160
|
+
"scan_repo",
|
|
121
161
|
type=str,
|
|
122
|
-
help="Repository to scan (username/repo
|
|
162
|
+
help="Repository to scan (username/repo, GitHub URL, SSH URL, etc.)"
|
|
123
163
|
)
|
|
124
|
-
@click.option("--
|
|
164
|
+
@click.option("--scan", is_flag=True, help="Perform vulnerability scanning")
|
|
125
165
|
@click.option("--sbom", is_flag=True, help="Generate Software Bill of Materials")
|
|
126
166
|
@click.option("--detect-secret", is_flag=True, help="Detect secrets in repository")
|
|
127
167
|
@click.option("--fullscan", is_flag=True, help="Perform all scan types")
|
|
128
168
|
@click.option("--branch", type=str, help="Specific branch to scan (defaults to main/master)")
|
|
129
169
|
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
|
|
130
|
-
@click.version_option(version="0.
|
|
170
|
+
@click.version_option(version="1.0.5", prog_name="prismor")
|
|
131
171
|
@click.pass_context
|
|
132
|
-
def cli(ctx,
|
|
172
|
+
def cli(ctx, scan_repo: Optional[str], scan: bool, sbom: bool, detect_secret: bool,
|
|
133
173
|
fullscan: bool, branch: Optional[str], output_json: bool):
|
|
134
174
|
"""Prismor CLI - Security scanning tool for GitHub repositories.
|
|
135
175
|
|
|
136
176
|
Examples:
|
|
137
|
-
prismor --
|
|
138
|
-
prismor --
|
|
139
|
-
prismor --
|
|
140
|
-
prismor --
|
|
177
|
+
prismor --repo username/repo --scan
|
|
178
|
+
prismor --repo username/repo --fullscan
|
|
179
|
+
prismor --repo https://github.com/username/repo --detect-secret
|
|
180
|
+
prismor --repo git@github.com:username/repo.git --sbom
|
|
181
|
+
prismor --repo github.com/username/repo --fullscan --branch develop
|
|
141
182
|
prismor status
|
|
142
183
|
prismor repos
|
|
143
184
|
"""
|
|
144
185
|
# If no command and no scan option, show help
|
|
145
|
-
if ctx.invoked_subcommand is None and not
|
|
186
|
+
if ctx.invoked_subcommand is None and not scan_repo:
|
|
146
187
|
click.echo(ctx.get_help())
|
|
147
188
|
return
|
|
148
189
|
|
|
149
190
|
# If scan option is provided, perform the scan
|
|
150
|
-
if
|
|
191
|
+
if scan_repo:
|
|
151
192
|
# Check if at least one scan type is selected
|
|
152
|
-
if not any([
|
|
153
|
-
print_error("Please specify at least one scan type: --
|
|
193
|
+
if not any([scan, sbom, detect_secret, fullscan]):
|
|
194
|
+
print_error("Please specify at least one scan type: --scan, --sbom, --detect-secret, or --fullscan")
|
|
154
195
|
sys.exit(1)
|
|
155
196
|
|
|
156
197
|
try:
|
|
157
198
|
# Initialize API client
|
|
158
|
-
print_info(f"Initializing Prismor scan for: {
|
|
199
|
+
print_info(f"Initializing Prismor scan for: {scan_repo}")
|
|
159
200
|
client = PrismorClient()
|
|
160
201
|
|
|
161
202
|
# Determine scan type for display
|
|
162
203
|
scan_types = []
|
|
163
204
|
if fullscan:
|
|
164
|
-
scan_types.append("Full Scan (
|
|
205
|
+
scan_types.append("Full Scan (scan + SBOM + Secret Detection)")
|
|
165
206
|
else:
|
|
166
|
-
if
|
|
167
|
-
scan_types.append("
|
|
207
|
+
if scan:
|
|
208
|
+
scan_types.append("scan")
|
|
168
209
|
if sbom:
|
|
169
210
|
scan_types.append("SBOM")
|
|
170
211
|
if detect_secret:
|
|
171
212
|
scan_types.append("Secret Detection")
|
|
172
213
|
|
|
173
214
|
print_info(f"Scan type: {', '.join(scan_types)}")
|
|
174
|
-
|
|
215
|
+
if scan or fullscan:
|
|
216
|
+
print_info("Starting scan... (vulnerability scans run asynchronously and may take up to 10 minutes)")
|
|
217
|
+
else:
|
|
218
|
+
print_info("Starting scan... (this may take a few minutes)")
|
|
219
|
+
|
|
220
|
+
# Show loading spinner during scan
|
|
221
|
+
spinner = Spinner("Scanning repository")
|
|
222
|
+
spinner.start()
|
|
175
223
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
224
|
+
try:
|
|
225
|
+
# Perform scan
|
|
226
|
+
results = client.scan(
|
|
227
|
+
repo=scan_repo,
|
|
228
|
+
scan=scan,
|
|
229
|
+
sbom=sbom,
|
|
230
|
+
detect_secret=detect_secret,
|
|
231
|
+
fullscan=fullscan,
|
|
232
|
+
branch=branch
|
|
233
|
+
)
|
|
234
|
+
spinner.stop()
|
|
235
|
+
except Exception as e:
|
|
236
|
+
spinner.stop()
|
|
237
|
+
raise e
|
|
185
238
|
|
|
186
239
|
# Output results
|
|
187
240
|
if output_json:
|
|
@@ -192,12 +245,8 @@ def cli(ctx, scan: Optional[str], vex: bool, sbom: bool, detect_secret: bool,
|
|
|
192
245
|
|
|
193
246
|
# Try to get repository ID and display dashboard link
|
|
194
247
|
try:
|
|
195
|
-
# Extract repo name from scan input
|
|
196
|
-
repo_name =
|
|
197
|
-
if scan.startswith("http://") or scan.startswith("https://"):
|
|
198
|
-
# Extract from GitHub URL
|
|
199
|
-
if "github.com/" in scan:
|
|
200
|
-
repo_name = scan.split("github.com/")[1].rstrip("/")
|
|
248
|
+
# Extract repo name from scan input using the comprehensive parser
|
|
249
|
+
repo_name = parse_github_repo(scan_repo)
|
|
201
250
|
|
|
202
251
|
# Get repository ID
|
|
203
252
|
repo_info = client.get_repository_by_name(repo_name)
|
|
@@ -236,7 +285,7 @@ def cli(ctx, scan: Optional[str], vex: bool, sbom: bool, detect_secret: bool,
|
|
|
236
285
|
@cli.command()
|
|
237
286
|
def version():
|
|
238
287
|
"""Display the version of Prismor CLI."""
|
|
239
|
-
click.echo("Prismor CLI
|
|
288
|
+
click.echo("Prismor CLI v1.0.5")
|
|
240
289
|
|
|
241
290
|
|
|
242
291
|
@cli.command()
|
|
@@ -256,6 +305,8 @@ def config():
|
|
|
256
305
|
print_success(f"PRISMOR_API_KEY: {masked_key}")
|
|
257
306
|
else:
|
|
258
307
|
print_error("PRISMOR_API_KEY: Not set")
|
|
308
|
+
click.echo("\nPlease specify your API key. You can generate one for free at:")
|
|
309
|
+
click.secho(" https://www.prismor.dev/cli", fg="cyan", underline=True)
|
|
259
310
|
click.echo("\nTo set your API key, run:")
|
|
260
311
|
click.echo(" export PRISMOR_API_KEY=your_api_key")
|
|
261
312
|
|
|
@@ -268,7 +319,14 @@ def repos():
|
|
|
268
319
|
"""List your connected repositories."""
|
|
269
320
|
try:
|
|
270
321
|
client = PrismorClient()
|
|
271
|
-
|
|
322
|
+
spinner = Spinner("Loading repositories")
|
|
323
|
+
spinner.start()
|
|
324
|
+
try:
|
|
325
|
+
repos_data = client.get_repositories()
|
|
326
|
+
spinner.stop()
|
|
327
|
+
except Exception as e:
|
|
328
|
+
spinner.stop()
|
|
329
|
+
raise e
|
|
272
330
|
|
|
273
331
|
click.echo("\n" + "=" * 60)
|
|
274
332
|
click.secho(" Your Repositories", fg="cyan", bold=True)
|
|
@@ -299,12 +357,216 @@ def repos():
|
|
|
299
357
|
sys.exit(1)
|
|
300
358
|
|
|
301
359
|
|
|
360
|
+
@cli.command()
|
|
361
|
+
@click.argument("repo", type=str)
|
|
362
|
+
@click.option("--branch", type=str, help="Specific branch to scan (defaults to main)")
|
|
363
|
+
@click.option("--token", type=str, help="GitHub token (or set GITHUB_TOKEN env var)")
|
|
364
|
+
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
|
|
365
|
+
def start_scan(repo: str, branch: Optional[str], token: Optional[str], output_json: bool):
|
|
366
|
+
"""Start a vulnerability scan and return a job_id for status checking.
|
|
367
|
+
|
|
368
|
+
REPO is the repository to scan (username/repo, GitHub URL, SSH URL, etc.)
|
|
369
|
+
|
|
370
|
+
This command starts a scan asynchronously and returns immediately with a job_id.
|
|
371
|
+
Use 'prismor scan-status <job_id>' to check when the scan completes.
|
|
372
|
+
|
|
373
|
+
Note: Requires GitHub token. Set GITHUB_TOKEN environment variable or use --token option.
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
prismor start-scan username/repo
|
|
377
|
+
prismor start-scan https://github.com/username/repo --branch develop
|
|
378
|
+
prismor start-scan username/repo --token ghp_xxxxx
|
|
379
|
+
prismor start-scan username/repo --json
|
|
380
|
+
"""
|
|
381
|
+
try:
|
|
382
|
+
client = PrismorClient()
|
|
383
|
+
print_info(f"Starting vulnerability scan for: {repo}")
|
|
384
|
+
if branch:
|
|
385
|
+
print_info(f"Branch: {branch}")
|
|
386
|
+
|
|
387
|
+
spinner = Spinner("Starting scan")
|
|
388
|
+
spinner.start()
|
|
389
|
+
try:
|
|
390
|
+
result = client.start_vulnerability_scan(repo, branch, token)
|
|
391
|
+
spinner.stop()
|
|
392
|
+
except Exception as e:
|
|
393
|
+
spinner.stop()
|
|
394
|
+
raise e
|
|
395
|
+
|
|
396
|
+
if output_json:
|
|
397
|
+
click.echo(json.dumps(result, indent=2))
|
|
398
|
+
else:
|
|
399
|
+
click.echo("\n" + "=" * 60)
|
|
400
|
+
click.secho(" Scan Started", fg="cyan", bold=True)
|
|
401
|
+
click.echo("=" * 60 + "\n")
|
|
402
|
+
|
|
403
|
+
job_id = result.get("job_id")
|
|
404
|
+
if job_id:
|
|
405
|
+
print_success(f"Scan started successfully!")
|
|
406
|
+
click.echo()
|
|
407
|
+
click.secho(f"Job ID: {job_id}", fg="yellow", bold=True)
|
|
408
|
+
click.echo()
|
|
409
|
+
click.secho("Repository:", fg="yellow", bold=True)
|
|
410
|
+
click.echo(f" {result.get('repository', repo)}")
|
|
411
|
+
click.echo()
|
|
412
|
+
if "branch" in result:
|
|
413
|
+
click.secho("Branch:", fg="yellow", bold=True)
|
|
414
|
+
click.echo(f" {result['branch']}")
|
|
415
|
+
click.echo()
|
|
416
|
+
click.secho("Status:", fg="yellow", bold=True)
|
|
417
|
+
click.echo(f" {result.get('status', 'accepted')}")
|
|
418
|
+
click.echo()
|
|
419
|
+
click.secho("Next Steps:", fg="cyan", bold=True)
|
|
420
|
+
click.echo(f" Check scan status with:")
|
|
421
|
+
click.secho(f" prismor scan-status {job_id}", fg="green", bold=True)
|
|
422
|
+
click.echo()
|
|
423
|
+
else:
|
|
424
|
+
print_error("Failed to get job_id from response")
|
|
425
|
+
click.echo(json.dumps(result, indent=2))
|
|
426
|
+
|
|
427
|
+
click.echo("=" * 60 + "\n")
|
|
428
|
+
|
|
429
|
+
except PrismorAPIError as e:
|
|
430
|
+
print_error(str(e))
|
|
431
|
+
sys.exit(1)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
print_error(f"Unexpected error: {str(e)}")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
@cli.command()
|
|
438
|
+
@click.argument("job_id", type=str)
|
|
439
|
+
@click.option("--json", "output_json", is_flag=True, help="Output results in JSON format")
|
|
440
|
+
def scan_status(job_id: str, output_json: bool):
|
|
441
|
+
"""Check the status of a vulnerability scan job.
|
|
442
|
+
|
|
443
|
+
JOB_ID is the job ID returned when starting a scan.
|
|
444
|
+
|
|
445
|
+
Examples:
|
|
446
|
+
prismor scan-status a724a4663cda4bf087ad171683cb726d
|
|
447
|
+
prismor scan-status 50cbe253e5634227b81fe744c2a0b3e7 --json
|
|
448
|
+
"""
|
|
449
|
+
try:
|
|
450
|
+
client = PrismorClient()
|
|
451
|
+
print_info(f"Checking scan status for job: {job_id}")
|
|
452
|
+
|
|
453
|
+
spinner = Spinner("Checking status")
|
|
454
|
+
spinner.start()
|
|
455
|
+
try:
|
|
456
|
+
status_data = client.check_scan_status(job_id)
|
|
457
|
+
spinner.stop()
|
|
458
|
+
except Exception as e:
|
|
459
|
+
spinner.stop()
|
|
460
|
+
raise e
|
|
461
|
+
|
|
462
|
+
if output_json:
|
|
463
|
+
click.echo(json.dumps(status_data, indent=2))
|
|
464
|
+
else:
|
|
465
|
+
click.echo("\n" + "=" * 60)
|
|
466
|
+
click.secho(" Scan Status", fg="cyan", bold=True)
|
|
467
|
+
click.echo("=" * 60 + "\n")
|
|
468
|
+
|
|
469
|
+
click.secho(f"Job ID: {status_data.get('job_id', job_id)}", fg="yellow", bold=True)
|
|
470
|
+
click.echo()
|
|
471
|
+
|
|
472
|
+
status = status_data.get("status", "unknown")
|
|
473
|
+
if status == "completed":
|
|
474
|
+
print_success(f"Status: {status}")
|
|
475
|
+
click.echo()
|
|
476
|
+
|
|
477
|
+
if "repository" in status_data:
|
|
478
|
+
click.secho("Repository:", fg="yellow", bold=True)
|
|
479
|
+
click.echo(f" {status_data['repository']}")
|
|
480
|
+
click.echo()
|
|
481
|
+
|
|
482
|
+
if "branch" in status_data:
|
|
483
|
+
click.secho("Branch:", fg="yellow", bold=True)
|
|
484
|
+
click.echo(f" {status_data['branch']}")
|
|
485
|
+
click.echo()
|
|
486
|
+
|
|
487
|
+
if "duration" in status_data:
|
|
488
|
+
click.secho("Duration:", fg="yellow", bold=True)
|
|
489
|
+
click.echo(f" {status_data['duration']:.2f} seconds")
|
|
490
|
+
click.echo()
|
|
491
|
+
|
|
492
|
+
if "public_url" in status_data:
|
|
493
|
+
click.secho("Results URL:", fg="yellow", bold=True)
|
|
494
|
+
click.secho(f" {status_data['public_url']}", fg="green")
|
|
495
|
+
click.echo()
|
|
496
|
+
|
|
497
|
+
if "presigned_url" in status_data:
|
|
498
|
+
click.secho("Presigned URL (expires in 1 hour):", fg="yellow", bold=True)
|
|
499
|
+
click.secho(f" {status_data['presigned_url']}", fg="blue")
|
|
500
|
+
click.echo()
|
|
501
|
+
|
|
502
|
+
# Display vulnerability summary if available
|
|
503
|
+
if "summary" in status_data:
|
|
504
|
+
summary = status_data["summary"]
|
|
505
|
+
click.secho("Vulnerability Summary:", fg="yellow", bold=True)
|
|
506
|
+
click.echo(f" Total Vulnerabilities: {summary.get('total_vulnerabilities', 0)}")
|
|
507
|
+
click.echo(f" Total Targets Scanned: {summary.get('total_targets', 0)}")
|
|
508
|
+
click.echo()
|
|
509
|
+
|
|
510
|
+
severity_breakdown = summary.get('severity_breakdown', {})
|
|
511
|
+
if severity_breakdown:
|
|
512
|
+
click.secho(" Severity Breakdown:", fg="yellow")
|
|
513
|
+
if severity_breakdown.get('CRITICAL', 0) > 0:
|
|
514
|
+
click.secho(f" CRITICAL: {severity_breakdown['CRITICAL']}", fg="red", bold=True)
|
|
515
|
+
if severity_breakdown.get('HIGH', 0) > 0:
|
|
516
|
+
click.secho(f" HIGH: {severity_breakdown['HIGH']}", fg="red")
|
|
517
|
+
if severity_breakdown.get('MEDIUM', 0) > 0:
|
|
518
|
+
click.secho(f" MEDIUM: {severity_breakdown['MEDIUM']}", fg="yellow")
|
|
519
|
+
if severity_breakdown.get('LOW', 0) > 0:
|
|
520
|
+
click.secho(f" LOW: {severity_breakdown['LOW']}", fg="blue")
|
|
521
|
+
if severity_breakdown.get('UNKNOWN', 0) > 0:
|
|
522
|
+
click.secho(f" UNKNOWN: {severity_breakdown['UNKNOWN']}", fg="white")
|
|
523
|
+
click.echo()
|
|
524
|
+
|
|
525
|
+
if "scan_date" in status_data:
|
|
526
|
+
click.secho("Scan Date:", fg="yellow", bold=True)
|
|
527
|
+
click.echo(f" {status_data['scan_date']}")
|
|
528
|
+
click.echo()
|
|
529
|
+
|
|
530
|
+
elif status == "running":
|
|
531
|
+
print_info(f"Status: {status}")
|
|
532
|
+
if "message" in status_data:
|
|
533
|
+
click.echo(f" {status_data['message']}")
|
|
534
|
+
click.echo()
|
|
535
|
+
click.echo("The scan is still in progress. Check back in a few moments.")
|
|
536
|
+
click.echo()
|
|
537
|
+
|
|
538
|
+
elif status == "failed":
|
|
539
|
+
print_error(f"Status: {status}")
|
|
540
|
+
if "error" in status_data:
|
|
541
|
+
click.echo(f" Error: {status_data['error']}")
|
|
542
|
+
click.echo()
|
|
543
|
+
else:
|
|
544
|
+
click.secho(f"Status: {status}", fg="yellow")
|
|
545
|
+
click.echo()
|
|
546
|
+
|
|
547
|
+
click.echo("=" * 60 + "\n")
|
|
548
|
+
|
|
549
|
+
except PrismorAPIError as e:
|
|
550
|
+
print_error(str(e))
|
|
551
|
+
sys.exit(1)
|
|
552
|
+
except Exception as e:
|
|
553
|
+
print_error(f"Unexpected error: {str(e)}")
|
|
554
|
+
sys.exit(1)
|
|
555
|
+
|
|
556
|
+
|
|
302
557
|
@cli.command()
|
|
303
558
|
def status():
|
|
304
559
|
"""Check your account status and GitHub integration."""
|
|
305
560
|
try:
|
|
306
561
|
client = PrismorClient()
|
|
307
|
-
|
|
562
|
+
spinner = Spinner("Checking account status")
|
|
563
|
+
spinner.start()
|
|
564
|
+
try:
|
|
565
|
+
auth_data = client.authenticate()
|
|
566
|
+
spinner.stop()
|
|
567
|
+
except Exception as e:
|
|
568
|
+
spinner.stop()
|
|
569
|
+
raise e
|
|
308
570
|
|
|
309
571
|
click.echo("\n" + "=" * 60)
|
|
310
572
|
click.secho(" Account Status", fg="cyan", bold=True)
|
|
@@ -312,9 +574,7 @@ def status():
|
|
|
312
574
|
|
|
313
575
|
user_info = auth_data.get("user", {})
|
|
314
576
|
if user_info:
|
|
315
|
-
|
|
316
|
-
click.echo()
|
|
317
|
-
|
|
577
|
+
|
|
318
578
|
repositories = user_info.get("repositories", [])
|
|
319
579
|
click.secho(f"Connected Repositories: {len(repositories)}", fg="green")
|
|
320
580
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: prismor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: A CLI tool for scanning GitHub repositories for vulnerabilities, secrets, and generating SBOMs
|
|
5
5
|
Home-page: https://github.com/PrismorSec/prismor-cli
|
|
6
6
|
Author: Prismor
|
|
@@ -48,12 +48,12 @@ A powerful command-line tool for scanning GitHub repositories for security vulne
|
|
|
48
48
|
|
|
49
49
|
## Features
|
|
50
50
|
|
|
51
|
-
- 🔍 **Vulnerability Scanning (
|
|
51
|
+
- 🔍 **Vulnerability Scanning (scan)** - Detect security vulnerabilities in your codebase
|
|
52
52
|
- 🔐 **Secret Detection** - Find exposed secrets, API keys, and credentials
|
|
53
53
|
- 📦 **SBOM Generation** - Generate comprehensive Software Bill of Materials
|
|
54
54
|
- ⚡ **Full Scan** - Run all security checks in one command
|
|
55
55
|
- 🎨 **Beautiful CLI Output** - Colorful, easy-to-read results
|
|
56
|
-
- 🔗 **Flexible Repository Input** - Support for
|
|
56
|
+
- 🔗 **Flexible Repository Input** - Support for multiple GitHub URL formats including SSH, HTTPS, and bare domain formats
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
59
59
|
|
|
@@ -61,7 +61,7 @@ A powerful command-line tool for scanning GitHub repositories for security vulne
|
|
|
61
61
|
2. **Generate your API Key** from the dashboard
|
|
62
62
|
3. **Install** the CLI: `pip install prismor`
|
|
63
63
|
4. **Set your API key**: `export PRISMOR_API_KEY=your_api_key`
|
|
64
|
-
5. **Run your first scan**: `prismor --
|
|
64
|
+
5. **Run your first scan**: `prismor --repo username/repo --fullscan`
|
|
65
65
|
|
|
66
66
|
For the complete analysis with dashboards and reports, visit [Prismor.dev](https://prismor.dev) after running scans!
|
|
67
67
|
|
|
@@ -116,39 +116,91 @@ This allows Prismor to securely access and scan your private repositories.
|
|
|
116
116
|
### Basic Syntax
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
prismor --
|
|
119
|
+
prismor --repo <repository> [scan-type]
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
**Note**: The `--scan` flag is used to enable vulnerability scanning, while `--repo` specifies the repository to scan.
|
|
123
|
+
|
|
122
124
|
### Repository Format
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
Prismor CLI supports multiple GitHub repository URL formats for maximum flexibility:
|
|
127
|
+
|
|
128
|
+
#### 1. **Username/Repository format** (recommended):
|
|
129
|
+
```bash
|
|
130
|
+
prismor --repo Ar9av/trychai-web-revamped --fullscan
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### 2. **HTTPS URLs**:
|
|
134
|
+
```bash
|
|
135
|
+
prismor --repo https://github.com/Ar9av/trychai-web-revamped --fullscan
|
|
136
|
+
prismor --repo https://www.github.com/Ar9av/trychai-web-revamped --fullscan
|
|
137
|
+
prismor --repo https://github.com/Ar9av/trychai-web-revamped.git --fullscan
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### 3. **HTTP URLs**:
|
|
141
|
+
```bash
|
|
142
|
+
prismor --repo http://github.com/Ar9av/trychai-web-revamped --fullscan
|
|
143
|
+
prismor --repo http://www.github.com/Ar9av/trychai-web-revamped --fullscan
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### 4. **Bare domain formats**:
|
|
147
|
+
```bash
|
|
148
|
+
prismor --repo github.com/Ar9av/trychai-web-revamped --fullscan
|
|
149
|
+
prismor --repo www.github.com/Ar9av/trychai-web-revamped --fullscan
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### 5. **SSH format**:
|
|
153
|
+
```bash
|
|
154
|
+
prismor --repo git@github.com:Ar9av/trychai-web-revamped.git --fullscan
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### 6. **URLs with paths and fragments**:
|
|
158
|
+
```bash
|
|
159
|
+
prismor --repo https://github.com/Ar9av/trychai-web-revamped/tree/main --fullscan
|
|
160
|
+
prismor --repo https://github.com/Ar9av/trychai-web-revamped/blob/main/file.py --fullscan
|
|
161
|
+
prismor --repo https://github.com/Ar9av/trychai-web-revamped#branch --fullscan
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**All formats are automatically parsed and normalized to extract the `user/repo_name` format for processing.**
|
|
125
165
|
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
prismor --scan Ar9av/trychai-web-revamped --fullscan
|
|
129
|
-
```
|
|
166
|
+
### Smart URL Parsing
|
|
130
167
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
168
|
+
Prismor CLI features intelligent GitHub URL parsing that automatically:
|
|
169
|
+
|
|
170
|
+
- ✅ **Detects and extracts** repository information from any supported format
|
|
171
|
+
- ✅ **Handles edge cases** like trailing slashes, `.git` suffixes, and branch references
|
|
172
|
+
- ✅ **Validates input** to ensure it's a valid GitHub repository
|
|
173
|
+
- ✅ **Provides clear error messages** for unsupported formats
|
|
174
|
+
- ✅ **Supports special characters** in repository names (hyphens, underscores, numbers)
|
|
175
|
+
|
|
176
|
+
**Examples of what gets automatically parsed:**
|
|
177
|
+
```bash
|
|
178
|
+
# All of these resolve to "facebook/react":
|
|
179
|
+
prismor --repo facebook/react --scan
|
|
180
|
+
prismor --repo https://github.com/facebook/react --scan
|
|
181
|
+
prismor --repo git@github.com:facebook/react.git --scan
|
|
182
|
+
prismor --repo github.com/facebook/react --scan
|
|
183
|
+
prismor --repo https://github.com/facebook/react/tree/main --scan
|
|
184
|
+
```
|
|
135
185
|
|
|
136
186
|
### Scan Types
|
|
137
187
|
|
|
138
|
-
#### 1. Vulnerability Scanning (
|
|
188
|
+
#### 1. Vulnerability Scanning (scan)
|
|
139
189
|
|
|
140
190
|
Scan for security vulnerabilities in your dependencies and code:
|
|
141
191
|
|
|
142
192
|
```bash
|
|
143
|
-
prismor --
|
|
193
|
+
prismor --repo myrepository --scan
|
|
144
194
|
```
|
|
145
195
|
|
|
196
|
+
**Note**: Vulnerability scans now run asynchronously for large repositories. The CLI will wait for completion automatically, but you can also use `prismor start-scan` to get a job ID and check status separately.
|
|
197
|
+
|
|
146
198
|
#### 2. Secret Detection
|
|
147
199
|
|
|
148
200
|
Detect exposed secrets, API keys, passwords, and other sensitive information:
|
|
149
201
|
|
|
150
202
|
```bash
|
|
151
|
-
prismor --
|
|
203
|
+
prismor --repo myrepository --detect-secret
|
|
152
204
|
```
|
|
153
205
|
|
|
154
206
|
#### 3. SBOM Generation
|
|
@@ -156,15 +208,15 @@ prismor --scan myrepository --detect-secret
|
|
|
156
208
|
Generate a Software Bill of Materials for your repository:
|
|
157
209
|
|
|
158
210
|
```bash
|
|
159
|
-
prismor --
|
|
211
|
+
prismor --repo myrepository --sbom
|
|
160
212
|
```
|
|
161
213
|
|
|
162
214
|
#### 4. Full Scan
|
|
163
215
|
|
|
164
|
-
Run all security checks (
|
|
216
|
+
Run all security checks (scan + Secret Detection + SBOM):
|
|
165
217
|
|
|
166
218
|
```bash
|
|
167
|
-
prismor --
|
|
219
|
+
prismor --repo myrepository --fullscan
|
|
168
220
|
```
|
|
169
221
|
|
|
170
222
|
### Multiple Scan Types
|
|
@@ -172,7 +224,7 @@ prismor --scan myrepository --fullscan
|
|
|
172
224
|
You can combine multiple scan types:
|
|
173
225
|
|
|
174
226
|
```bash
|
|
175
|
-
prismor --
|
|
227
|
+
prismor --repo myrepository --scan --detect-secret
|
|
176
228
|
```
|
|
177
229
|
|
|
178
230
|
### JSON Output
|
|
@@ -180,37 +232,127 @@ prismor --scan myrepository --vex --detect-secret
|
|
|
180
232
|
Get results in JSON format for automation and integration:
|
|
181
233
|
|
|
182
234
|
```bash
|
|
183
|
-
prismor --
|
|
235
|
+
prismor --repo myrepository --fullscan --json
|
|
184
236
|
```
|
|
185
237
|
|
|
186
238
|
## Examples
|
|
187
239
|
|
|
188
|
-
### Example 1: Quick Vulnerability Scan
|
|
240
|
+
### Example 1: Quick Vulnerability Scan (Username/Repo format)
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
prismor --repo facebook/react --scan
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Example 2: Comprehensive Security Audit (HTTPS URL)
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
prismor --repo https://github.com/microsoft/vscode --fullscan
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Example 3: Secret Detection with SSH URL
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
prismor --repo git@github.com:openai/gpt-3.git --detect-secret
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Example 4: SBOM Generation with Bare Domain
|
|
189
259
|
|
|
190
260
|
```bash
|
|
191
|
-
prismor --
|
|
261
|
+
prismor --repo github.com/kubernetes/kubernetes --sbom --json > sbom-results.json
|
|
192
262
|
```
|
|
193
263
|
|
|
194
|
-
### Example
|
|
264
|
+
### Example 5: Full Scan with Branch Reference
|
|
195
265
|
|
|
196
266
|
```bash
|
|
197
|
-
prismor --
|
|
267
|
+
prismor --repo https://github.com/tensorflow/tensorflow/tree/v2.13.0 --fullscan
|
|
198
268
|
```
|
|
199
269
|
|
|
200
|
-
### Example
|
|
270
|
+
### Example 6: Multiple Scan Types with Different URL Formats
|
|
201
271
|
|
|
202
272
|
```bash
|
|
203
|
-
|
|
273
|
+
# Using HTTPS URL
|
|
274
|
+
prismor --repo https://github.com/pytorch/pytorch --scan --sbom
|
|
275
|
+
|
|
276
|
+
# Using SSH URL
|
|
277
|
+
prismor --repo git@github.com:nodejs/node.git --detect-secret --sbom
|
|
278
|
+
|
|
279
|
+
# Using bare domain
|
|
280
|
+
prismor --repo www.github.com/vercel/next.js --fullscan
|
|
204
281
|
```
|
|
205
282
|
|
|
206
|
-
### Example
|
|
283
|
+
### Example 7: Async Scan with Status Checking
|
|
207
284
|
|
|
208
285
|
```bash
|
|
209
|
-
|
|
286
|
+
# Start a scan and get job ID
|
|
287
|
+
prismor start-scan username/repo --branch main
|
|
288
|
+
|
|
289
|
+
# Check scan status (use job ID from previous command)
|
|
290
|
+
prismor scan-status <job_id>
|
|
291
|
+
|
|
292
|
+
# Check status with JSON output
|
|
293
|
+
prismor scan-status <job_id> --json
|
|
210
294
|
```
|
|
211
295
|
|
|
212
296
|
## Additional Commands
|
|
213
297
|
|
|
298
|
+
### Start Async Vulnerability Scan
|
|
299
|
+
|
|
300
|
+
Start a vulnerability scan asynchronously and get a job ID for status checking:
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
prismor start-scan username/repo
|
|
304
|
+
prismor start-scan username/repo --branch develop
|
|
305
|
+
prismor start-scan username/repo --token ghp_xxxxx
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Note**: Requires GitHub token. Set `GITHUB_TOKEN` environment variable or use `--token` option.
|
|
309
|
+
|
|
310
|
+
### Check Scan Status
|
|
311
|
+
|
|
312
|
+
Check the status of a running or completed vulnerability scan:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
prismor scan-status <job_id>
|
|
316
|
+
prismor scan-status <job_id> --json
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Status Response Includes**:
|
|
320
|
+
- Job status (running/completed/failed)
|
|
321
|
+
- Repository and branch information
|
|
322
|
+
- Results URLs (public and presigned)
|
|
323
|
+
- Vulnerability summary with severity breakdown
|
|
324
|
+
- Scan date and duration
|
|
325
|
+
|
|
326
|
+
**Example Output**:
|
|
327
|
+
```
|
|
328
|
+
============================================================
|
|
329
|
+
Scan Status
|
|
330
|
+
============================================================
|
|
331
|
+
|
|
332
|
+
Job ID: abc123def456...
|
|
333
|
+
|
|
334
|
+
Status: completed
|
|
335
|
+
|
|
336
|
+
Repository:
|
|
337
|
+
https://github.com/username/repo
|
|
338
|
+
|
|
339
|
+
Branch:
|
|
340
|
+
main
|
|
341
|
+
|
|
342
|
+
Vulnerability Summary:
|
|
343
|
+
Total Vulnerabilities: 15
|
|
344
|
+
Total Targets Scanned: 3
|
|
345
|
+
|
|
346
|
+
Severity Breakdown:
|
|
347
|
+
CRITICAL: 2
|
|
348
|
+
HIGH: 5
|
|
349
|
+
MEDIUM: 6
|
|
350
|
+
LOW: 2
|
|
351
|
+
|
|
352
|
+
Results URL:
|
|
353
|
+
https://prismor-sbom-public-dev.s3.amazonaws.com/...
|
|
354
|
+
```
|
|
355
|
+
|
|
214
356
|
### Check Configuration
|
|
215
357
|
|
|
216
358
|
View your current Prismor CLI configuration:
|
|
@@ -303,9 +445,24 @@ export PRISMOR_API_KEY=your_api_key_here
|
|
|
303
445
|
|
|
304
446
|
### Invalid Repository Format
|
|
305
447
|
|
|
306
|
-
Ensure your repository is in one of
|
|
307
|
-
|
|
448
|
+
Ensure your repository is in one of the supported formats:
|
|
449
|
+
|
|
450
|
+
**Supported formats:**
|
|
451
|
+
- `username/repository` (recommended)
|
|
308
452
|
- `https://github.com/username/repository`
|
|
453
|
+
- `https://www.github.com/username/repository`
|
|
454
|
+
- `http://github.com/username/repository`
|
|
455
|
+
- `http://www.github.com/username/repository`
|
|
456
|
+
- `github.com/username/repository`
|
|
457
|
+
- `www.github.com/username/repository`
|
|
458
|
+
- `git@github.com:username/repository.git`
|
|
459
|
+
- `https://github.com/username/repository/tree/branch`
|
|
460
|
+
- `https://github.com/username/repository/blob/branch/file`
|
|
461
|
+
|
|
462
|
+
**Not supported:**
|
|
463
|
+
- Non-GitHub URLs (GitLab, Bitbucket, etc.)
|
|
464
|
+
- Invalid URL formats
|
|
465
|
+
- Empty or malformed repository names
|
|
309
466
|
|
|
310
467
|
### Connection Issues
|
|
311
468
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
prismor/__init__.py,sha256=_1mv0XdQk8KdVm_Fua-h0nBJJS3SrZIKOPJYhi8Vt00,230
|
|
2
|
+
prismor/api.py,sha256=ydcovvL5ior_dpQByoYfCySoO1jIbtjrwr8iCjnBa58,19020
|
|
3
|
+
prismor/cli.py,sha256=k_3IAIntTNyV8Gkw2gNwDoH_B_dFZXC_sZ9V98C_9MQ,24197
|
|
4
|
+
prismor-1.0.5.dist-info/licenses/LICENSE,sha256=qWFF8Eh6gpZOq_3effdd6hfeMN2WN9ZG4vOyFk2MyhU,1065
|
|
5
|
+
prismor-1.0.5.dist-info/METADATA,sha256=1TIjbE3iKGy0U46NWYsxKkS0lVtAjLR8eHXB5Wjaizo,14319
|
|
6
|
+
prismor-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
prismor-1.0.5.dist-info/entry_points.txt,sha256=Uiu0HW04eq2Gb6sQC9o-LqMKMyW1SKwkojxrkFeVfqg,45
|
|
8
|
+
prismor-1.0.5.dist-info/top_level.txt,sha256=nlJGoJ3fQXRL27RXQ5LJU2LX1kl1VSgKXyKjcSR28lw,8
|
|
9
|
+
prismor-1.0.5.dist-info/RECORD,,
|
prismor-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
prismor/__init__.py,sha256=6sRGygr6VrNie8Xd_B9Zeq6Q0ThWRftLBnVzZBdGEb4,230
|
|
2
|
-
prismor/api.py,sha256=YrFnw1adT4ci6ehR4qB03kSOxw9l1o4fm0KSGUlDU4s,8886
|
|
3
|
-
prismor/cli.py,sha256=K0aOxtbhE-gUoRw7selqT1a7BTr80A4Ogvvcspx5BUk,13582
|
|
4
|
-
prismor-0.1.2.dist-info/licenses/LICENSE,sha256=qWFF8Eh6gpZOq_3effdd6hfeMN2WN9ZG4vOyFk2MyhU,1065
|
|
5
|
-
prismor-0.1.2.dist-info/METADATA,sha256=sDwR9KP4wlNOdQON-j-42Sabowz2q7IhCXb4dss8s1I,9394
|
|
6
|
-
prismor-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
-
prismor-0.1.2.dist-info/entry_points.txt,sha256=Uiu0HW04eq2Gb6sQC9o-LqMKMyW1SKwkojxrkFeVfqg,45
|
|
8
|
-
prismor-0.1.2.dist-info/top_level.txt,sha256=nlJGoJ3fQXRL27RXQ5LJU2LX1kl1VSgKXyKjcSR28lw,8
|
|
9
|
-
prismor-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|