1- import click
2- import subprocess
3- import os
41import json
2+ import os
3+ import subprocess
4+ import sys
5+
6+ import click
7+
8+
9+ def resolve_base_path (specified_path ):
10+ base_path = specified_path or os .getenv ("WORKING_DIR" , "" )
11+ if os .path .exists (base_path ):
12+ return base_path
13+
14+ relative = os .path .join ("." , base_path )
15+ if os .path .exists (relative ):
16+ return relative
17+
18+ raise FileNotFoundError (f"Paths '{ base_path } ' and '{ relative } ' do not exist." )
19+
20+
21+ def build_detect_secrets_cmd (path , baseline_file , all_files , baseline ):
22+ cmd = [
23+ "detect-secrets" ,
24+ "-C" ,
25+ path ,
26+ "scan" ,
27+ "--exclude-files" ,
28+ baseline_file ,
29+ ]
530
6- # https://github.com/Yelp/detect-secrets
7- @click .command ('detect_secrets' , help = "Runs detect-secrets to check if there are any plaintext secret in the source code. By default it only checks tracked files." )
8- @click .argument ('specified_path' , required = False )
9- @click .option ('--all_files' , is_flag = True , help = "Scans all files." )
10- @click .option ('--baseline' , is_flag = True , help = "Use baseline file" )
11- @click .option ('--add_to_baseline' , is_flag = True , help = "Adds all detected secrets to baseline file to don't show them as secrets." )
12- def detect_secrets (specified_path , all_files , baseline , add_to_baseline ):
13- if specified_path :
14- base_path = specified_path
15- else :
16- base_path = os .path .join (os .getenv ('WORKING_DIR' , '' ))
17- if not os .path .exists (base_path ):
18- relative_base_path = './' + base_path
19- if not os .path .exists (relative_base_path ):
20- click .echo (click .style (f"Paths '{ base_path } ' and '{ relative_base_path } ' does not exist." , fg = 'red' ))
21- exit (1 )
22- base_path = relative_base_path
23-
24- click .echo (f"Running detect-secrets for the path '{ base_path } '" )
25- test_path = base_path
26- baseline_file = os .path .join (test_path , '.secrets.baseline' )
27-
28- detect_secrets_cmd = ['detect-secrets' , '-C' , test_path , 'scan' , '--exclude-files' , baseline_file ]
2931 if all_files :
30- detect_secrets_cmd . extend ([ ' --all-files' ] )
31-
32+ cmd . append ( " --all-files" )
33+
3234 if baseline :
33- if os .path .exists (baseline_file ):
34- detect_secrets_cmd .extend (['--baseline' ,baseline_file ])
35- else :
36- click .echo (click .style (f"Baseline file '{ baseline_file } ' does not exists. You should run --add_to_baseline first" , fg = 'red' ))
37- exit (1 )
35+ cmd .extend (["--baseline" , baseline_file ])
36+
37+ return cmd
38+
39+
40+ def run_scan (cmd , write_to = None ):
41+ if write_to :
42+ with open (write_to , "w" ) as file :
43+ subprocess .run (cmd , stdout = file , check = True )
44+ return None
45+
46+ result = subprocess .run (
47+ cmd ,
48+ capture_output = True ,
49+ text = True ,
50+ check = True ,
51+ )
52+ return result .stdout
53+
54+
55+ def parse_results (raw_output ):
56+ data = json .loads (raw_output )
57+ return data .get ("results" , {})
58+
59+
60+ def render_results (secrets , baseline_file , show_hash ):
61+ if not secrets :
62+ click .echo (click .style ("No secrets found!" , fg = "green" ))
63+ sys .exit (0 )
64+
65+ contains_real = False
66+
67+ for file , entries in secrets .items ():
68+ for entry in entries :
69+ if not entry .get ("is_secret" , True ):
70+ continue
71+
72+ contains_real = True
73+ line = entry ["line_number" ]
74+
75+ click .echo (
76+ click .style (
77+ f"{ file } :{ line } " ,
78+ fg = "yellow" ,
79+ )
80+ )
81+
82+ if show_hash :
83+ click .echo (
84+ click .style (
85+ f" Hashed: { entry ['hashed_secret' ]} " ,
86+ fg = "red" ,
87+ )
88+ )
89+
90+ if contains_real :
91+ click .echo (
92+ click .style (
93+ "Secrets detected." ,
94+ fg = "red" ,
95+ )
96+ )
97+ click .echo (
98+ click .style (
99+ f"Run 'detect-secrets audit { baseline_file } ' " "to review false positives." ,
100+ fg = "cyan" ,
101+ )
102+ )
103+ sys .exit (1 )
104+
105+ click .echo (
106+ click .style (
107+ "All secrets are managed in baseline." ,
108+ fg = "green" ,
109+ )
110+ )
111+ sys .exit (0 )
112+
113+
114+ @click .command ("detect_secrets" )
115+ @click .argument ("specified_path" , required = False )
116+ @click .option ("--all-files" , is_flag = True )
117+ @click .option ("--baseline" , is_flag = True )
118+ @click .option ("--add-to-baseline" , is_flag = True )
119+ @click .option (
120+ "--show-hash" ,
121+ is_flag = True ,
122+ help = "Display hashed secrets in output." ,
123+ )
124+ def detect_secrets (
125+ specified_path ,
126+ all_files ,
127+ baseline ,
128+ add_to_baseline ,
129+ show_hash ,
130+ ):
131+ try :
132+ base_path = resolve_base_path (specified_path )
133+ except FileNotFoundError as e :
134+ click .echo (click .style (str (e ), fg = "red" ))
135+ sys .exit (1 )
136+
137+ baseline_file = os .path .join (base_path , ".secrets.baseline" )
138+
139+ cmd = build_detect_secrets_cmd (
140+ base_path ,
141+ baseline_file ,
142+ all_files ,
143+ baseline ,
144+ )
145+
38146 try :
39147 if add_to_baseline :
40- file = open (baseline_file ,"w" )
41- subprocess .run (detect_secrets_cmd , stdout = file )
42- file .close ()
43- click .echo (click .style (f"Done! You may now run 'rosemary detect_secrets --all_files --baseline' to inspect secrets. Baseline is updated automatically" , fg = 'green' ))
44- click .echo (click .style (f"You shouldn't need to run this command again" , fg = 'red' ))
45- click .echo (click .style (f"You can also just delete the secrets leaked or run 'detect-secrets audit { baseline_file } ' to discard false positives" , fg = 'green' ))
46- return
47-
48- if baseline :
49- subprocess .run (detect_secrets_cmd )
50- file = open (baseline_file ,"r" )
51- output = file .read ()
52-
53- file .close ()
54- else :
55- output = subprocess .run (detect_secrets_cmd , capture_output = True , check = True , text = True ).stdout
56-
57- output = json .loads (output )
58- secrets = output ["results" ]
59- if secrets == {}:
60- click .echo (click .style ("No secrets found!" , fg = 'green' ))
61- exit (0 )
62- else :
63-
64- contains_secrets = False
65- for file in secrets :
66- line = secrets [file ][0 ]["line_number" ]
67- secret = secrets [file ][0 ]["hashed_secret" ]
68-
69- is_secret = True
70- if "is_secret" in secrets [file ][0 ]:
71- is_secret = secrets [file ][0 ]["is_secret" ]
72-
73- if is_secret :
74- contains_secrets = True
75- click .echo (click .style (f"File { file } in line { line } " , fg = 'yellow' ))
76- click .echo (click .style (f"\t Hashed secret(not displayed for security) { secret } " , fg = 'red' ))
77- if contains_secrets :
78- click .echo (click .style ("########## Secrets found! ##########" , fg = 'red' ))
79- if baseline :
80- click .echo (click .style (f"You may now run 'detect-secrets audit { baseline_file } ' to discard false positives" , fg = 'green' ))
81- click .echo (click .style (f"If the secrets listed are not false positives you can remove them by editing the listed lines and rerunning this command" , fg = 'red' ))
82- exit (1 )
83- else :
84- click .echo (click .style ("########## All secrets are managed ##########" , fg = 'green' ))
85- exit (0 )
86-
148+ run_scan (cmd , write_to = baseline_file )
149+ click .echo (click .style ("Baseline updated." , fg = "green" ))
150+ sys .exit (0 )
151+
152+ raw_output = run_scan (cmd )
153+ secrets = parse_results (raw_output )
154+ render_results (
155+ secrets ,
156+ baseline_file ,
157+ show_hash ,
158+ )
159+
87160 except subprocess .CalledProcessError as e :
88- click .echo (click .style (f"Error running detect_secrets: { e } " , fg = 'red' ))
161+ click .echo (
162+ click .style (
163+ f"Error running detect-secrets: { e } " ,
164+ fg = "red" ,
165+ )
166+ )
167+ sys .exit (1 )
0 commit comments