@@ -12,8 +12,10 @@ use rocket::{
1212 serde:: json:: { json, Json , Value } ,
1313 Build , Rocket , Shutdown , State ,
1414} ;
15+ use secrets:: model:: secret_result:: SecretResult ;
1516use server:: model:: analysis_request:: ServerRule ;
1617use server:: model:: analysis_response:: AnalysisResponse ;
18+ use server:: model:: secret_scan:: { SecretScanRequest , SecretScanResponse } ;
1719use server:: model:: {
1820 analysis_request:: AnalysisRequest , tree_sitter_tree_request:: TreeSitterRequest ,
1921} ;
@@ -151,6 +153,82 @@ async fn analyze(
151153 . unwrap ( )
152154}
153155
156+ fn process_secret_scan_request ( request : SecretScanRequest ) -> Result < Vec < SecretResult > , String > {
157+ // Maximum number of rules per request to prevent excessive CPU usage.
158+ const MAX_RULES_COUNT : usize = 1000 ;
159+
160+ if request. rules . is_empty ( ) {
161+ return Err ( "No rules provided" . to_string ( ) ) ;
162+ }
163+
164+ if request. rules . len ( ) > MAX_RULES_COUNT {
165+ return Err ( format ! (
166+ "Too many rules: {} exceeds maximum of {}" ,
167+ request. rules. len( ) ,
168+ MAX_RULES_COUNT
169+ ) ) ;
170+ }
171+
172+ // Validate filename (prevent path traversal attacks)
173+ if request. filename . contains ( ".." ) || request. filename . contains ( '\0' ) {
174+ return Err ( "Invalid filename: path traversal detected" . to_string ( ) ) ;
175+ }
176+
177+ // Deserialize rules from JSON
178+ let rules: Vec < secrets:: model:: secret_rule:: SecretRule > = request
179+ . rules
180+ . iter ( )
181+ . map ( |r| serde_json:: from_value ( r. clone ( ) ) )
182+ . collect :: < Result < Vec < _ > , _ > > ( )
183+ . map_err ( |e| format ! ( "Failed to parse rules: {}" , e) ) ?;
184+
185+ // Build the scanner with the provided rules
186+ let scanner = secrets:: scanner:: build_sds_scanner ( & rules, request. use_debug ) ?;
187+
188+ // Configure analysis options
189+ let options = common:: analysis_options:: AnalysisOptions {
190+ use_debug : request. use_debug ,
191+ ..Default :: default ( )
192+ } ;
193+
194+ // Perform the secret scan
195+ let results = secrets:: scanner:: find_secrets (
196+ & scanner,
197+ & rules,
198+ & request. filename ,
199+ & request. data ,
200+ & options,
201+ ) ;
202+
203+ Ok ( results)
204+ }
205+
206+ /// Scans source code for secrets using the provided detection rules.
207+ #[ rocket:: post( "/scan-secrets" , format = "application/json" , data = "<request>" ) ]
208+ async fn scan_secrets ( span : TraceSpan , request : Json < SecretScanRequest > ) -> Value {
209+ let _entered = span. enter ( ) ;
210+
211+ rocket:: tokio:: task:: spawn_blocking ( move || {
212+ let request = request. into_inner ( ) ;
213+ let ( rule_responses, errors) = match process_secret_scan_request ( request) {
214+ Ok ( resp) => ( resp, vec ! [ ] ) ,
215+ Err ( err) => ( vec ! [ ] , vec ! [ err] ) ,
216+ } ;
217+
218+ json ! ( SecretScanResponse {
219+ rule_responses,
220+ errors,
221+ } )
222+ } )
223+ . await
224+ . unwrap_or_else ( |e| {
225+ json ! ( SecretScanResponse {
226+ rule_responses: vec![ ] ,
227+ errors: vec![ format!( "Internal error: {e}" ) ] ,
228+ } )
229+ } )
230+ }
231+
154232#[ rocket:: post( "/get-treesitter-ast" , format = "application/json" , data = "<request>" ) ]
155233fn get_tree ( span : TraceSpan , request : Json < TreeSitterRequest > ) -> Value {
156234 let _entered = span. enter ( ) ;
@@ -207,6 +285,7 @@ fn mount_endpoints(rocket: Rocket<Build>) -> Rocket<Build> {
207285 "/" ,
208286 rocket:: routes![
209287 analyze,
288+ scan_secrets,
210289 get_tree,
211290 get_version,
212291 get_revision,
0 commit comments