@@ -84,7 +84,7 @@ public class AssessService {
8484 name = "get_vulnerability" ,
8585 description =
8686 "Takes a vulnerability ID (vulnID) and application ID (appID) and returns details about"
87- + " the specific security vulnerability. Use list_applications_with_name first to get "
87+ + " the specific security vulnerability. Use search_applications(name=...) to find "
8888 + " the application ID from a name. If based on the stacktrace, the vulnerability"
8989 + " looks like it is in code that is not in the codebase, the vulnerability may be in"
9090 + " a 3rd party library, review the CVE data attached to that stackframe you believe"
@@ -191,7 +191,7 @@ private Optional<LibraryLibraryObservation> findMatchingLibraryData(
191191 name = "list_vulnerabilities" ,
192192 description =
193193 "Takes an application ID (appID) and returns a list of vulnerabilities. Use"
194- + " list_applications_with_name first to get the application ID from a name. Remember"
194+ + " search_applications(name=...) to find the application ID from a name. Remember"
195195 + " to include the vulnID in the response." )
196196 public List <VulnLight > listVulnsByAppId (@ ToolParam (description = "Application ID" ) String appID )
197197 throws IOException {
@@ -229,7 +229,7 @@ public List<VulnLight> listVulnsByAppId(@ToolParam(description = "Application ID
229229 description =
230230 "Takes an application ID (appID) and session metadata in the form of name / value. and"
231231 + " returns a list of vulnerabilities matching that application ID and session"
232- + " metadata. Use list_applications_with_name first to get the application ID from a"
232+ + " metadata. Use search_applications(name=...) to find the application ID from a"
233233 + " name." )
234234 public List <VulnLight > listVulnsByAppIdAndSessionMetadata (
235235 @ ToolParam (description = "Application ID" ) String appID ,
@@ -270,7 +270,7 @@ public List<VulnLight> listVulnsByAppIdAndSessionMetadata(
270270 "Takes an application ID (appID) and returns a list of vulnerabilities for the latest"
271271 + " session matching that application ID. This is useful for getting the most recent"
272272 + " vulnerabilities without needing to specify session metadata. Use"
273- + " list_applications_with_name first to get the application ID from a name." )
273+ + " search_applications(name=...) to find the application ID from a name." )
274274 public List <VulnLight > listVulnsByAppIdForLatestSession (
275275 @ ToolParam (description = "Application ID" ) String appID ) throws IOException {
276276 log .info ("Listing vulnerabilities for application: {}" , appID );
@@ -312,197 +312,15 @@ public List<VulnLight> listVulnsByAppIdForLatestSession(
312312 name = "get_session_metadata" ,
313313 description =
314314 "Retrieves session metadata for a specific application by its ID. Returns the latest"
315- + " session metadata for the application. Use list_applications_with_name first to "
316- + " get the application ID from a name." )
315+ + " session metadata for the application. Use search_applications(name=...) to find "
316+ + " the application ID from a name." )
317317 public MetadataFilterResponse getSessionMetadata (
318318 @ ToolParam (description = "Application ID" ) String appId ) throws IOException {
319319 var contrastSDK =
320320 SDKHelper .getSDK (hostName , apiKey , serviceKey , userName , httpProxyHost , httpProxyPort );
321321 return contrastSDK .getSessionMetadataForApplication (orgID , appId , null );
322322 }
323323
324- @ Tool (
325- name = "list_applications_with_name" ,
326- description =
327- "Takes an application name (app_name) returns a list of active applications that contain"
328- + " that name. Please remember to display the name, status and ID." )
329- public List <ApplicationData > getApplications (
330- @ ToolParam (description = "Application name (supports partial matching, case-insensitive)" )
331- String app_name )
332- throws IOException {
333- log .info ("Listing active applications matching name: {}" , app_name );
334- var contrastSDK =
335- SDKHelper .getSDK (hostName , apiKey , serviceKey , userName , httpProxyHost , httpProxyPort );
336- try {
337- var applications = SDKHelper .getApplicationsWithCache (orgID , contrastSDK );
338- log .debug ("Retrieved {} total applications from Contrast" , applications .size ());
339-
340- var filteredApps = new ArrayList <ApplicationData >();
341- for (Application app : applications ) {
342- if (app .getName ().toLowerCase ().contains (app_name .toLowerCase ())) {
343- filteredApps .add (
344- new ApplicationData (
345- app .getName (),
346- app .getStatus (),
347- app .getAppId (),
348- FilterHelper .formatTimestamp (app .getLastSeen ()),
349- app .getLanguage (),
350- getMetadataFromApp (app ),
351- app .getTags (),
352- app .getTechs ()));
353- log .debug (
354- "Found matching application - ID: {}, Name: {}, Status: {}" ,
355- app .getAppId (),
356- app .getName (),
357- app .getStatus ());
358- }
359- }
360- if (filteredApps .isEmpty ()) {
361- SDKHelper .clearApplicationsCache ();
362- for (Application app : applications ) {
363- if (app .getName ().toLowerCase ().contains (app_name .toLowerCase ())) {
364- filteredApps .add (
365- new ApplicationData (
366- app .getName (),
367- app .getStatus (),
368- app .getAppId (),
369- FilterHelper .formatTimestamp (app .getLastSeen ()),
370- app .getLanguage (),
371- getMetadataFromApp (app ),
372- app .getTags (),
373- app .getTechs ()));
374- log .debug (
375- "Found matching application - ID: {}, Name: {}, Status: {}" ,
376- app .getAppId (),
377- app .getName (),
378- app .getStatus ());
379- }
380- }
381- }
382-
383- log .info ("Found {} applications matching '{}'" , filteredApps .size (), app_name );
384- return filteredApps ;
385- } catch (Exception e ) {
386- log .error ("Error listing applications matching name: {}" , app_name , e );
387- throw new IOException ("Failed to list applications: " + e .getMessage (), e );
388- }
389- }
390-
391- @ Tool (
392- name = "get_applications_by_tag" ,
393- description = "Takes a tag name and returns a list of applications that have that tag." )
394- public List <ApplicationData > getAllApplicationsByTag (
395- @ ToolParam (description = "Tag name to filter by" ) String tag ) throws IOException {
396- log .info ("Retrieving applications with tag: {}" , tag );
397- var allApps = getAllApplications ();
398- log .debug ("Retrieved {} total applications, filtering by tag" , allApps .size ());
399-
400- var filteredApps = allApps .stream ().filter (app -> app .tags ().contains (tag )).toList ();
401-
402- log .info ("Found {} applications with tag '{}'" , filteredApps .size (), tag );
403- return filteredApps ;
404- }
405-
406- @ Tool (
407- name = "get_applications_by_metadata" ,
408- description =
409- "Takes a metadata name and value and returns a list of applications that have that"
410- + " metadata name value pair." )
411- public List <ApplicationData > getApplicationsByMetadata (
412- @ ToolParam (description = "Metadata field name (case-insensitive)" ) String metadata_name ,
413- @ ToolParam (description = "Metadata field value (case-insensitive)" ) String metadata_value )
414- throws IOException {
415- log .info (
416- "Retrieving applications with metadata - Name: {}, Value: {}" ,
417- metadata_name ,
418- metadata_value );
419- var allApps = getAllApplications ();
420- log .debug ("Retrieved {} total applications, filtering by metadata" , allApps .size ());
421-
422- var filteredApps =
423- allApps .stream ()
424- .filter (
425- app ->
426- app .metadata () != null
427- && app .metadata ().stream ()
428- .anyMatch (
429- m ->
430- m != null
431- && m .name () != null
432- && m .name ().equalsIgnoreCase (metadata_name )
433- && m .value () != null
434- && m .value ().equalsIgnoreCase (metadata_value )))
435- .toList ();
436-
437- log .info (
438- "Found {} applications with metadata - Name: {}, Value: {}" ,
439- filteredApps .size (),
440- metadata_name ,
441- metadata_value );
442- return filteredApps ;
443- }
444-
445- @ Tool (
446- name = "get_applications_by_metadata_name" ,
447- description = "Takes a metadata name a list of applications that have that metadata name." )
448- public List <ApplicationData > getApplicationsByMetadataName (
449- @ ToolParam (description = "Metadata field name (case-insensitive)" ) String metadata_name )
450- throws IOException {
451- log .info ("Retrieving applications with metadata - Name: {}" , metadata_name );
452- var allApps = getAllApplications ();
453- log .debug ("Retrieved {} total applications, filtering by metadata" , allApps .size ());
454-
455- var filteredApps =
456- allApps .stream ()
457- .filter (
458- app ->
459- app .metadata () != null
460- && app .metadata ().stream ()
461- .anyMatch (
462- m ->
463- m != null
464- && m .name () != null
465- && m .name ().equalsIgnoreCase (metadata_name )))
466- .toList ();
467-
468- log .info ("Found {} applications with metadata - Name: {}" , filteredApps .size (), metadata_name );
469- return filteredApps ;
470- }
471-
472- @ Tool (
473- name = "list_all_applications" ,
474- description = "Takes no argument and list all the applications" )
475- public List <ApplicationData > getAllApplications () throws IOException {
476- log .info ("Listing all applications" );
477- var contrastSDK =
478- SDKHelper .getSDK (hostName , apiKey , serviceKey , userName , httpProxyHost , httpProxyPort );
479- try {
480- var applications = SDKHelper .getApplicationsWithCache (orgID , contrastSDK );
481- log .debug ("Retrieved {} total applications from Contrast" , applications .size ());
482-
483- var returnedApps = new ArrayList <ApplicationData >();
484- for (Application app : applications ) {
485- returnedApps .add (
486- new ApplicationData (
487- app .getName (),
488- app .getStatus (),
489- app .getAppId (),
490- FilterHelper .formatTimestamp (app .getLastSeen ()),
491- app .getLanguage (),
492- getMetadataFromApp (app ),
493- app .getTags (),
494- app .getTechs ()));
495- }
496-
497- log .info ("Found {} applications" , returnedApps .size ());
498- return returnedApps ;
499-
500- } catch (Exception e ) {
501- log .error ("Error listing all applications" , e );
502- throw new IOException ("Failed to list applications: " + e .getMessage (), e );
503- }
504- }
505-
506324 @ Tool (
507325 name = "search_applications" ,
508326 description =
@@ -564,50 +382,13 @@ public List<ApplicationData> search_applications(
564382 var filteredApps = new ArrayList <ApplicationData >();
565383
566384 for (Application app : applications ) {
567- // Apply name filter if provided
568- if (StringUtils .hasText (name )
569- && !app .getName ().toLowerCase ().contains (name .toLowerCase ())) {
570- continue ;
571- }
572-
573- // Apply tag filter if provided (case-sensitive)
574- if (StringUtils .hasText (tag ) && !app .getTags ().contains (tag )) {
385+ // Apply all filters - skip if any filter doesn't match
386+ if (!matchesNameFilter (app , name )
387+ || !matchesTagFilter (app , tag )
388+ || !matchesMetadataFilter (app , metadataName , metadataValue )) {
575389 continue ;
576390 }
577391
578- // Apply metadata filter if provided
579- if (hasMetadataName ) {
580- var hasMatchingMetadata = false ;
581-
582- if (app .getMetadataEntities () != null ) {
583- for (var metadata : app .getMetadataEntities ()) {
584- if (metadata != null && metadata .getName () != null ) {
585- var nameMatches = metadata .getName ().equalsIgnoreCase (metadataName );
586-
587- if (hasMetadataValue ) {
588- // Both name and value must match
589- if (nameMatches
590- && metadata .getValue () != null
591- && metadata .getValue ().equalsIgnoreCase (metadataValue )) {
592- hasMatchingMetadata = true ;
593- break ;
594- }
595- } else {
596- // Name only - any value is acceptable
597- if (nameMatches ) {
598- hasMatchingMetadata = true ;
599- break ;
600- }
601- }
602- }
603- }
604- }
605-
606- if (!hasMatchingMetadata ) {
607- continue ;
608- }
609- }
610-
611392 // Application passed all filters
612393 filteredApps .add (
613394 new ApplicationData (
@@ -643,6 +424,79 @@ public List<ApplicationData> search_applications(
643424 }
644425 }
645426
427+ /**
428+ * Check if an application matches the name filter (partial, case-insensitive).
429+ *
430+ * @param app The application to check
431+ * @param name The name filter to match against (null/empty means no filter)
432+ * @return true if the application matches the filter or no filter is specified
433+ */
434+ private boolean matchesNameFilter (Application app , String name ) {
435+ if (!StringUtils .hasText (name )) {
436+ return true ; // No filter specified
437+ }
438+ return app .getName ().toLowerCase ().contains (name .toLowerCase ());
439+ }
440+
441+ /**
442+ * Check if an application matches the tag filter (exact, case-sensitive).
443+ *
444+ * @param app The application to check
445+ * @param tag The tag filter to match against (null/empty means no filter)
446+ * @return true if the application matches the filter or no filter is specified
447+ */
448+ private boolean matchesTagFilter (Application app , String tag ) {
449+ if (!StringUtils .hasText (tag )) {
450+ return true ; // No filter specified
451+ }
452+ return app .getTags ().contains (tag );
453+ }
454+
455+ /**
456+ * Check if an application matches the metadata filter (exact, case-insensitive).
457+ *
458+ * @param app The application to check
459+ * @param metadataName The metadata field name to match (null/empty means no filter)
460+ * @param metadataValue The metadata field value to match (null/empty means any value)
461+ * @return true if the application matches the filter or no filter is specified
462+ */
463+ private boolean matchesMetadataFilter (
464+ Application app , String metadataName , String metadataValue ) {
465+ if (!StringUtils .hasText (metadataName )) {
466+ return true ; // No filter specified
467+ }
468+
469+ var hasMetadataValue = StringUtils .hasText (metadataValue );
470+
471+ if (app .getMetadataEntities () == null ) {
472+ return false ; // No metadata to match against
473+ }
474+
475+ for (var metadata : app .getMetadataEntities ()) {
476+ if (metadata == null || metadata .getName () == null ) {
477+ continue ;
478+ }
479+
480+ var nameMatches = metadata .getName ().equalsIgnoreCase (metadataName );
481+
482+ if (hasMetadataValue ) {
483+ // Both name and value must match
484+ if (nameMatches
485+ && metadata .getValue () != null
486+ && metadata .getValue ().equalsIgnoreCase (metadataValue )) {
487+ return true ;
488+ }
489+ } else {
490+ // Name only - any value is acceptable
491+ if (nameMatches ) {
492+ return true ;
493+ }
494+ }
495+ }
496+
497+ return false ; // No matching metadata found
498+ }
499+
646500 @ Tool (
647501 name = "list_all_vulnerabilities" ,
648502 description =
0 commit comments