diff --git a/.gitignore b/.gitignore index 549e00a..0babd78 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ build/ ### VS Code ### .vscode/ + +### contrast specific items ### +config.json +*.ipynb \ No newline at end of file diff --git a/README.md b/README.md index 4dce832..f427eb1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,21 @@ # Contrast MCP Server -[![Java CI with Maven](https://github.com/Contrast-Labs/mcp-contrast/actions/workflows/build.yml/badge.svg)](https://github.com/Contrast-Labs/mcp-contrast/actions/workflows/build.yml) -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Java CI with Maven](https://github.com/Contrast-Labs/mcp-contrast/actions/workflows/build.yml/badge.svg)](https://github.com/Contrast-Labs/mcp-contrast/actions/workflows/buil**Recent Changes (v0.0.9+)**: +- Added comprehensive risk tolerance controls to AssessService, ADRService, and SastService +- Added risk tolerance controls to SCAService (MEDIUM for library listings, HIGH for CVE data) +- Added risk tolerance controls to RouteCoverageService (MEDIUM for all route coverage functions) +- All vulnerability-related functions now require appropriate risk tolerance levels +- Only one function remains unrestricted: `list_application_libraries_by_app_id` in SCAService +- Default risk tolerance changed from unrestricted to `NO_RISK` for enhanced security + +**Future Considerations**: +- The remaining unrestricted function may receive risk controls based on data sensitivity analysis +- Additional granular risk controls may be added based on user feedback + +**Backward Compatibility**: +- Existing configurations without `ACCEPTED_RISK_TOLERANCE` will default to `NO_RISK` +- Users must explicitly set risk tolerance to access previously unrestricted functions +- This change enhances security but may require configuration updates for existing deploymentsse](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Maven Central](https://img.shields.io/maven-central/v/com.contrast.labs/mcp-contrast.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.contrast.labs%22%20AND%20a:%22mcp-contrast%22) [![Install in VS Code Docker](https://img.shields.io/badge/VS_Code-docker-0098FF?style=flat-square&logo=githubcopilot&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=contrastmcp&config=%7B%22command%22:%22docker%22,%22args%22:%5B%22run%22,%20%22-e%22,%22CONTRAST_HOST_NAME%22,%20%22-e%22,%22CONTRAST_API_KEY%22,%20%22-e%22,%22CONTRAST_SERVICE_KEY%22,%20%22-e%22,%22CONTRAST_USERNAME%22,%20%22-e%22,%22CONTRAST_ORG_ID%22,%20%20%22-i%22,%20%22--rm%22,%20%22contrast/mcp-contrast:latest%22,%20%22-t%22,%20%22stdio%22%5D,%22env%22:%7B%22CONTRAST_HOST_NAME%22:%22example.contrastsecurity.com%22,%22CONTRAST_API_KEY%22:%22example%22,%22CONTRAST_SERVICE_KEY%22:%22example%22,%22CONTRAST_USERNAME%22:%22example@example.com%22,%22CONTRAST_ORG_ID%22:%22example%22%7D%7D) @@ -71,6 +85,146 @@ Depending on what questions you ask the following information will be provided t * Route Coverage data * ADR/Protect Attack Event Details +## Risk Tolerance and Data Sensitivity Controls + +The Contrast MCP Server implements a risk-based access control system to protect sensitive security data from being inadvertently exposed to public LLMs. This is controlled via the `ACCEPTED_RISK_TOLERANCE` environment variable. + +### Risk Levels + +The system supports the following risk tolerance levels (from most restrictive to least restrictive): + +- **`ACCEPT_NO_RISK`** (0) - Completely safe, no data access allowed +- **`LOW`** (1) - Minimal potential impact, basic application metadata only +- **`MEDIUM`** (2) - Moderate potential impact, includes vulnerability listings +- **`HIGH`** (3) - Significant potential impact, includes detailed vulnerability data +- **`ACCEPT_ALL_RISK`** (4) - No security restrictions, all data accessible + +### Function Risk Requirements + +Each MCP function requires a minimum risk tolerance level based on the sensitivity of the data it exposes. The risk tolerance checks are implemented across all service layers: + +#### LOW Risk Functions (AssessService) +- `list_all_applications` - Lists basic application information (name, status, ID, language) +- `list_applications_with_name` - Searches applications by name +- `get_applications_by_tag` - Filters applications by tags +- `get_applications_by_metadata` - Filters applications by metadata +- `get_applications_by_metadata_name` - Filters applications by metadata name +- `list_session_metadata_for_application` - Lists session metadata for applications + +#### LOW Risk Functions (SastService) +- `list_Scan_Project` - Returns scan project details + +#### MEDIUM Risk Functions (AssessService) +- `list_vulnerabilities` - Lists vulnerability summaries for applications +- `list_vulnerabilities_with_id` - Lists vulnerabilities by application ID +- `list_vulnerabilities_by_application_and_session_metadata` - Filtered vulnerability listings +- `list_vulnerabilities_by_application_and_latest_session` - Latest session vulnerability data + +#### MEDIUM Risk Functions (ADRService) +- `get_ADR_Protect_Rules` - Returns protection/ADR rules for application by name +- `get_ADR_Protect_Rules_by_app_id` - Returns protection/ADR rules for application by ID + +#### MEDIUM Risk Functions (SCAService) +- `list_application_libraries` - Returns libraries used in application by name +- `list_application_libraries_by_app_id` - Returns libraries used in application by ID + +#### MEDIUM Risk Functions (RouteCoverageService) +- `get_application_route_coverage` - Returns route coverage data for application by name +- `get_application_route_coverage_by_app_id` - Returns route coverage data for application by ID +- `get_application_route_coverage_by_app_name_and_session_metadata` - Returns route coverage filtered by session metadata +- `get_application_route_coverage_by_app_id_and_session_metadata` - Returns route coverage by app ID and session metadata +- `get_application_route_coverage_by_app_name_latest_session` - Returns route coverage for latest session by app name +- `get_application_route_coverage_by_app_id_latest_session` - Returns route coverage for latest session by app ID + +#### HIGH Risk Functions (AssessService) +- `get_vulnerability_by_id` - Detailed vulnerability information including stack traces +- `get_vulnerability` - Detailed vulnerability data by name and ID + +#### HIGH Risk Functions (SastService) +- `list_Scan_Results` - Returns latest scan results in SARIF format + +#### HIGH Risk Functions (SCAService) +- `list_applications_vulnerable_to_cve` - Returns applications and servers vulnerable to specific CVE + +### Configuration + +Set the risk tolerance in your environment configuration: + +```bash +# For maximum security (recommended for public LLMs) +ACCEPTED_RISK_TOLERANCE=ACCEPT_NO_RISK + +# For basic application information only +ACCEPTED_RISK_TOLERANCE=LOW + +# For vulnerability listings (use with caution) +ACCEPTED_RISK_TOLERANCE=MEDIUM + +# For detailed vulnerability data (private LLMs only) +ACCEPTED_RISK_TOLERANCE=HIGH + +# For detail vulnerability data and moving forward anything to be added to always accept +ACCEPTED_RISK_TOLERANCE=ACCEPT_ALL_RISK +``` + +### Security Recommendations + +- **Public LLMs**: Use `ACCEPT_NO_RISK` or `LOW` only +- **Private/Local LLMs**: Use appropriate level based on your security requirements +- **Development/Testing**: Can use higher levels with proper data handling controls +- **Production Security Analysis**: Use `HIGH` only with verified private LLM instances +- **Production Security Analysis**: Use `ACCEPT_ALL_RISK` only with verified private LLM instances and with caution as it will accept everything without review + +⚠️ **WARNING**: Never use `MEDIUM` or `HIGH` risk tolerance with public LLMs as this will expose sensitive vulnerability data including stack traces, HTTP requests, and detailed security information to external services. + +### Implementation Details + +The risk tolerance system is implemented across multiple service classes: + +#### Service-Level Risk Controls +Each service class implements risk tolerance checks at the method level: + +- **AssessService**: Comprehensive risk controls for all vulnerability and application functions +- **ADRService**: Medium-level risk controls for protection rule access +- **SastService**: Low and High risk controls for scan data access +- **SCAService**: Medium and High risk controls for most functions (one function remains unrestricted) +- **RouteCoverageService**: Medium-level risk controls for all route coverage functions + +#### Risk Tolerance Configuration +The system uses the `RiskLevel` enum which maps string values to integer levels: +```java +ACCEPT_NO_RISK(0), LOW(1), MEDIUM(2), HIGH(3), ACCEPT_ALL_RISK(4) +``` + +Each protected method includes: +1. Risk tolerance parsing from environment variable +2. Logging of current operation and risk level +3. Access control check before executing sensitive operations +4. Appropriate error messages when access is denied + +#### Risk Tolerance Defaults +- **Default Risk Level**: `ACCEPT_NO_RISK` (0) - Most restrictive by default +- **Environment Variable**: `ACCEPTED_RISK_TOLERANCE` +- **Fallback Behavior**: If not set or invalid, defaults to `ACCEPT_NO_RISK` + +### Migration and Upgrade Notes + +**Recent Changes (v0.0.9+)**: +- Added comprehensive risk tolerance controls to AssessService, ADRService, and SastService +- All vulnerability-related functions now require appropriate risk tolerance levels +- SCAService and RouteCoverageService functions remain unrestricted (may be updated in future versions) +- Default risk tolerance changed from unrestricted to `ACCEPT_NO_RISK` for enhanced security + +**Future Considerations**: +- SCAService functions may be enhanced with risk controls in future versions +- RouteCoverageService functions may receive risk controls based on data sensitivity analysis +- Additional granular risk controls may be added based on user feedback + +**Backward Compatibility**: +- Existing configurations without `ACCEPTED_RISK_TOLERANCE` will default to `ACCEPT_NO_RISK` +- Users must explicitly set risk tolerance to access previously unrestricted functions +- This change enhances security but may require configuration updates for existing deployments + ## Build Requires Java 17+ @@ -82,17 +236,22 @@ To add the MCP Server to your local AI system, modify the config.json file and a ```json "mcpServers": { "contrast-mcp": { - "command": "/usr/bin/java", "args": ["-jar","/Users/name/workspace/mcp-contrast/mcp-contrast/target/mcp-contrast-0.0.1-SNAPSHOT.jar", + "command": "/usr/bin/java", + "args": [ + "-jar", + "/Users/name/workspace/mcp-contrast/mcp-contrast/target/mcp-contrast-0.0.1-SNAPSHOT.jar", "--CONTRAST_HOST_NAME=example.contrastsecurity.com", "--CONTRAST_API_KEY=xxx", "--CONTRAST_SERVICE_KEY=xxx", "--CONTRAST_USERNAME=xxx.xxx@contrastsecurity.com", - "--CONTRAST_ORG_ID=xxx"] + "--CONTRAST_ORG_ID=xxx", + "--ACCEPTED_RISK_TOLERANCE=LOW" + ] } } ``` -You obviously need to configure the above to match your contrast API Creds. +You obviously need to configure the above to match your contrast API credentials and set your desired risk tolerance level. ## Docker @@ -154,6 +313,8 @@ Then add the following to the settings.json file. "CONTRAST_USERNAME", "-e", "CONTRAST_ORG_ID", + "-e", + "ACCEPTED_RISK_TOLERANCE", "-i", "--rm", "contrast/mcp-contrast:latest", @@ -165,7 +326,8 @@ Then add the following to the settings.json file. "CONTRAST_API_KEY": "example", "CONTRAST_SERVICE_KEY": "example", "CONTRAST_USERNAME": "example@example.com", - "CONTRAST_ORG_ID": "example" + "CONTRAST_ORG_ID": "example", + "ACCEPTED_RISK_TOLERANCE": "LOW" } } } @@ -202,6 +364,8 @@ To install the MCP Server in Copilot for Intellij. "CONTRAST_USERNAME", "-e", "CONTRAST_ORG_ID", + "-e", + "ACCEPTED_RISK_TOLERANCE", "-i", "--rm", "contrast/mcp-contrast:latest", @@ -213,7 +377,8 @@ To install the MCP Server in Copilot for Intellij. "CONTRAST_API_KEY": "example", "CONTRAST_SERVICE_KEY": "example", "CONTRAST_USERNAME": "example@example.com", - "CONTRAST_ORG_ID": "example" + "CONTRAST_ORG_ID": "example", + "ACCEPTED_RISK_TOLERANCE": "LOW" } } } @@ -244,6 +409,8 @@ Add the following the json configuration "CONTRAST_USERNAME", "-e", "CONTRAST_ORG_ID", + "-e", + "ACCEPTED_RISK_TOLERANCE", "-i", "--rm", "contrast/mcp-contrast:latest", @@ -255,7 +422,8 @@ Add the following the json configuration "CONTRAST_API_KEY": "example", "CONTRAST_SERVICE_KEY": "example", "CONTRAST_USERNAME": "example@example.com", - "CONTRAST_ORG_ID": "example" + "CONTRAST_ORG_ID": "example", + "ACCEPTED_RISK_TOLERANCE": "LOW" }, "disabled": false, "autoApprove": [] @@ -290,6 +458,8 @@ Add the following configuration to the `claude_desktop_config.json` file: "CONTRAST_USERNAME", "-e", "CONTRAST_ORG_ID", + "-e", + "ACCEPTED_RISK_TOLERANCE", "-i", "--rm", "contrast/mcp-contrast:latest", @@ -301,7 +471,8 @@ Add the following configuration to the `claude_desktop_config.json` file: "CONTRAST_API_KEY": "xxx", "CONTRAST_SERVICE_KEY": "xxx", "CONTRAST_USERNAME": "xxx.xxx@example.com", - "CONTRAST_ORG_ID": "xxx" + "CONTRAST_ORG_ID": "xxx", + "ACCEPTED_RISK_TOLERANCE": "LOW" } } } @@ -348,7 +519,8 @@ When configuring in your config.json file, include the proxy settings in the arg "--CONTRAST_API_KEY=example", "--CONTRAST_SERVICE_KEY=example", "--CONTRAST_USERNAME=example@example.com", - "--CONTRAST_ORG_ID=example" + "--CONTRAST_ORG_ID=example", + "--ACCEPTED_RISK_TOLERANCE=LOW" ] } } @@ -367,6 +539,7 @@ docker run \ -e CONTRAST_SERVICE_KEY=example \ -e CONTRAST_USERNAME=example \ -e CONTRAST_ORG_ID=example \ + -e ACCEPTED_RISK_TOLERANCE=LOW \ -i \ contrast/mcp-contrast:latest \ -t stdio @@ -393,6 +566,8 @@ For VS Code configuration with Docker and proxy, modify the settings.json like t "CONTRAST_USERNAME", "-e", "CONTRAST_ORG_ID", + "-e", + "ACCEPTED_RISK_TOLERANCE", "-e", "http_proxy_host", "-e", "http_proxy_port", "-i", @@ -407,8 +582,9 @@ For VS Code configuration with Docker and proxy, modify the settings.json like t "CONTRAST_SERVICE_KEY": "example", "CONTRAST_USERNAME": "example@example.com", "CONTRAST_ORG_ID": "example", + "ACCEPTED_RISK_TOLERANCE": "LOW", "http_proxy_host": "proxy.example.com", - "http_proxy_port": "8080" + "http_proxy_port": "8080" } } } @@ -441,5 +617,3 @@ Failed to list applications: PKIX path building failed: sun.security.provider.ce If this occurs you will need to add the certificate to the Java Truststore and then add the following to the command line arguments when running the MCP server: `-Djavax.net.ssl.trustStore=/loctaion/to/mcp-truststore.jks, -Djavax.net.ssl.trustStorePassword=yourpassword` More details on how to do this can be found in the [Java documentation](https://docs.oracle.com/cd/E19509-01/820-3503/6nf1il6er/index.html). Or ask your LLM to help you with this. - - diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/ADRService.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/ADRService.java index 74a0d86..f2a7722 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/ADRService.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/ADRService.java @@ -34,6 +34,8 @@ public class ADRService { private static final Logger logger = LoggerFactory.getLogger(ADRService.class); + //This is the accepted risk tolerance level for running ADR operations. Default is ACCEPT_NO_RISK (0) + private int acceptedRiskTolerance = 0; @Value("${contrast.host-name:${CONTRAST_HOST_NAME:}}") private String hostName; @@ -56,76 +58,92 @@ public class ADRService { @Value("${http.proxy.port:${http_proxy_port:}}") private String httpProxyPort; + @Value("${accepted.risk.tolerance:${ACCEPTED_RISK_TOLERANCE:}}") + private String acceptedRiskToleranceStr; @Tool(name = "get_ADR_Protect_Rules", description = "takes a application name and returns the protect / adr rules for the application") public ProtectData getProtectData(String applicationName) throws IOException { - logger.info("Starting retrieval of protection rules for application: {}", applicationName); - long startTime = System.currentTimeMillis(); - - try { - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized successfully for application: {}", applicationName); - - // Get application ID from name - logger.debug("Looking up application ID for name: {}", applicationName); - Optional app = SDKHelper.getApplicationByName(applicationName, orgID, contrastSDK); - if (app.isEmpty()) { - logger.warn("No application ID found for application: {}", applicationName); - return null; + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Starting retrieval of protection rules for application: {}", applicationName); + long startTime = System.currentTimeMillis(); + + try { + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized successfully for application: {}", applicationName); + + // Get application ID from name + logger.debug("Looking up application ID for name: {}", applicationName); + Optional app = SDKHelper.getApplicationByName(applicationName, orgID, contrastSDK); + if (app.isEmpty()) { + logger.warn("No application ID found for application: {}", applicationName); + return null; + } + logger.debug("Found application ID: {} for application: {}", app.get().getAppId(), applicationName); + + ProtectData result = getProtectDataByAppID(app.get().getAppId()); + long duration = System.currentTimeMillis() - startTime; + logger.info("Completed retrieval of protection rules for application: {} (took {} ms)", applicationName, duration); + return result; + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + logger.error("Error retrieving protection rules for application: {} (after {} ms): {}", + applicationName, duration, e.getMessage(), e); + throw e; } - logger.debug("Found application ID: {} for application: {}", app.get().getAppId(), applicationName); - - ProtectData result = getProtectDataByAppID(app.get().getAppId()); - long duration = System.currentTimeMillis() - startTime; - logger.info("Completed retrieval of protection rules for application: {} (took {} ms)", applicationName, duration); - return result; - } catch (Exception e) { - long duration = System.currentTimeMillis() - startTime; - logger.error("Error retrieving protection rules for application: {} (after {} ms): {}", - applicationName, duration, e.getMessage(), e); - throw e; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @Tool(name = "get_ADR_Protect_Rules_by_app_id", description = "takes a application ID and returns the protect / adr rules for the application") public ProtectData getProtectDataByAppID(String appID) throws IOException { - if (appID == null || appID.isEmpty()) { - logger.error("Cannot retrieve protection rules - application ID is null or empty"); - throw new IllegalArgumentException("Application ID cannot be null or empty"); - } - - logger.info("Starting retrieval of protection rules for application ID: {}", appID); - long startTime = System.currentTimeMillis(); - - try { - // Initialize ContrastSDK - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized successfully for application ID: {}", appID); - - // Initialize SDK extension - SDKExtension extendedSDK = new SDKExtension(contrastSDK); - logger.debug("SDKExtension initialized successfully for application ID: {}", appID); - - // Get protect configuration - logger.debug("Retrieving protection configuration for application ID: {}", appID); - ProtectData protectData = extendedSDK.getProtectConfig(orgID, appID); - long duration = System.currentTimeMillis() - startTime; - - if (protectData == null) { - logger.warn("No protection data returned for application ID: {} (took {} ms)", appID, duration); - return null; + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + if (appID == null || appID.isEmpty()) { + logger.error("Cannot retrieve protection rules - application ID is null or empty"); + throw new IllegalArgumentException("Application ID cannot be null or empty"); } - int ruleCount = protectData.getRules() != null ? protectData.getRules().size() : 0; - logger.info("Successfully retrieved {} protection rules for application ID: {} (took {} ms)", - ruleCount, appID, duration); - return protectData; - } catch (Exception e) { - long duration = System.currentTimeMillis() - startTime; - logger.error("Error retrieving protection rules for application ID: {} (after {} ms): {}", - appID, duration, e.getMessage(), e); - throw e; + logger.info("Starting retrieval of protection rules for application ID: {}", appID); + long startTime = System.currentTimeMillis(); + + try { + // Initialize ContrastSDK + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized successfully for application ID: {}", appID); + + // Initialize SDK extension + SDKExtension extendedSDK = new SDKExtension(contrastSDK); + logger.debug("SDKExtension initialized successfully for application ID: {}", appID); + + // Get protect configuration + logger.debug("Retrieving protection configuration for application ID: {}", appID); + ProtectData protectData = extendedSDK.getProtectConfig(orgID, appID); + long duration = System.currentTimeMillis() - startTime; + + if (protectData == null) { + logger.warn("No protection data returned for application ID: {} (took {} ms)", appID, duration); + return null; + } + + int ruleCount = protectData.getRules() != null ? protectData.getRules().size() : 0; + logger.info("Successfully retrieved {} protection rules for application ID: {} (took {} ms)", + ruleCount, appID, duration); + return protectData; + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + logger.error("Error retrieving protection rules for application ID: {} (after {} ms): {}", + appID, duration, e.getMessage(), e); + throw e; + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/AssessService.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/AssessService.java index 46772f8..e5ee6c9 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/AssessService.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/AssessService.java @@ -8,7 +8,7 @@ * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, + * distributed under the License is distributed o n an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. @@ -45,6 +45,8 @@ public class AssessService { private static final Logger logger = LoggerFactory.getLogger(AssessService.class); + //This is the accepted risk tolerance level for running ADR operations. Default is ACCEPT_NO_RISK (0) + private int acceptedRiskTolerance = 0; @Value("${contrast.host-name:${CONTRAST_HOST_NAME:}}") private String hostName; @@ -67,73 +69,82 @@ public class AssessService { @Value("${http.proxy.port:${http_proxy_port:}}") private String httpProxyPort; - + @Value("${accepted.risk.tolerance:${ACCEPTED_RISK_TOLERANCE:}}") + private String acceptedRiskToleranceStr; @Tool(name = "get_vulnerability_by_id", description = "takes a vulnerability ID ( vulnID ) and Application ID ( appID ) and returns details about the specific security vulnerability. If based on the stacktrace, the vulnerability looks like it is in code that is not in the codebase, the vulnerability may be in a 3rd party library, review the CVE data attached to that stackframe you believe the vulnerability exists in and if possible upgrade that library to the next non vulnerable version based on the remediation guidance.") public Vulnerability getVulnerabilityById(String vulnID, String appID) throws IOException { - logger.info("Retrieving vulnerability details for vulnID: {} in application ID: {}", vulnID, appID); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized with host: {}", hostName); - - try { - Trace trace = contrastSDK.getTraces(orgID, appID, new TraceFilterBody()).getTraces().stream() - .filter(t -> t.getUuid().equalsIgnoreCase(vulnID)) - .findFirst() - .orElseThrow(); - logger.debug("Found trace with title: {} and rule: {}", trace.getTitle(), trace.getRule()); - - RecommendationResponse recommendationResponse = contrastSDK.getRecommendation(orgID, vulnID); - HttpRequestResponse requestResponse = contrastSDK.getHttpRequest(orgID, vulnID); - EventSummaryResponse eventSummaryResponse = contrastSDK.getEventSummary(orgID, vulnID); - - Optional triggerEvent = eventSummaryResponse.getEvents().stream() - .filter(e -> e.getType().equalsIgnoreCase("trigger")) - .findFirst(); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: high, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.HIGH.getValue()) { + + logger.info("Retrieving vulnerability details for vulnID: {} in application ID: {}", vulnID, appID); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized with host: {}", hostName); - List stackTraces = new ArrayList<>(); - if (triggerEvent.isPresent()) { - List sTrace = triggerEvent.get().getEvent().getStacktraces(); - if (sTrace != null) { - stackTraces.addAll(sTrace.stream().map(Stacktrace::getDescription).toList()); - logger.debug("Found {} stack traces for vulnerability", stackTraces.size()); + try { + Trace trace = contrastSDK.getTraces(orgID, appID, new TraceFilterBody()).getTraces().stream() + .filter(t -> t.getUuid().equalsIgnoreCase(vulnID)) + .findFirst() + .orElseThrow(); + logger.debug("Found trace with title: {} and rule: {}", trace.getTitle(), trace.getRule()); + + RecommendationResponse recommendationResponse = contrastSDK.getRecommendation(orgID, vulnID); + HttpRequestResponse requestResponse = contrastSDK.getHttpRequest(orgID, vulnID); + EventSummaryResponse eventSummaryResponse = contrastSDK.getEventSummary(orgID, vulnID); + + Optional triggerEvent = eventSummaryResponse.getEvents().stream() + .filter(e -> e.getType().equalsIgnoreCase("trigger")) + .findFirst(); + + List stackTraces = new ArrayList<>(); + if (triggerEvent.isPresent()) { + List sTrace = triggerEvent.get().getEvent().getStacktraces(); + if (sTrace != null) { + stackTraces.addAll(sTrace.stream().map(Stacktrace::getDescription).toList()); + logger.debug("Found {} stack traces for vulnerability", stackTraces.size()); + } } - } - List libs = SDKHelper.getLibsForID(appID,orgID, new SDKExtension(contrastSDK)); - List lobs = new ArrayList<>(); - for(LibraryExtended lib : libs) { - LibraryLibraryObservation llob = new LibraryLibraryObservation(lib, SDKHelper.getLibraryObservationsWithCache(lib.getHash(), appID, orgID, 50,new SDKExtension(contrastSDK))); - lobs.add(llob); - } - List stackLibs = new ArrayList<>(); - Set libsToReturn = new HashSet<>(); - for(String stackTrace : stackTraces) { - Optional matchingLlobOpt = findMatchingLibraryData(stackTrace, lobs); - if (matchingLlobOpt.isPresent()) { - LibraryLibraryObservation llob = matchingLlobOpt.get(); - LibraryExtended library = llob.library(); - if (!library.getVulnerabilities().isEmpty()) { - libsToReturn.add(library); // Set.add() handles uniqueness efficiently - stackLibs.add(new StackLib(stackTrace, library.getHash())); + List libs = SDKHelper.getLibsForID(appID,orgID, new SDKExtension(contrastSDK)); + List lobs = new ArrayList<>(); + for(LibraryExtended lib : libs) { + LibraryLibraryObservation llob = new LibraryLibraryObservation(lib, SDKHelper.getLibraryObservationsWithCache(lib.getHash(), appID, orgID, 50,new SDKExtension(contrastSDK))); + lobs.add(llob); + } + List stackLibs = new ArrayList<>(); + Set libsToReturn = new HashSet<>(); + for(String stackTrace : stackTraces) { + Optional matchingLlobOpt = findMatchingLibraryData(stackTrace, lobs); + if (matchingLlobOpt.isPresent()) { + LibraryLibraryObservation llob = matchingLlobOpt.get(); + LibraryExtended library = llob.library(); + if (!library.getVulnerabilities().isEmpty()) { + libsToReturn.add(library); // Set.add() handles uniqueness efficiently + stackLibs.add(new StackLib(stackTrace, library.getHash())); + } else { + stackLibs.add(new StackLib(stackTrace, null)); + } } else { stackLibs.add(new StackLib(stackTrace, null)); } - } else { - stackLibs.add(new StackLib(stackTrace, null)); } - } - String httpRequestText = null; - if( requestResponse.getHttpRequest()!=null) { - httpRequestText = requestResponse.getHttpRequest().getText(); + String httpRequestText = null; + if( requestResponse.getHttpRequest()!=null) { + httpRequestText = requestResponse.getHttpRequest().getText(); + } + String hint = HintGenerator.generateVulnerabilityFixHint(trace.getRule()); + logger.info("Successfully retrieved vulnerability details for vulnID: {}", vulnID); + return new Vulnerability(hint, vulnID, trace.getTitle(), trace.getRule(), + recommendationResponse.getRecommendation().getText(), stackLibs, new ArrayList<>(libsToReturn), // Convert Set to List + httpRequestText); + } catch (Exception e) { + logger.error("Error retrieving vulnerability details for vulnID: {}", vulnID, e); + throw new IOException("Failed to retrieve vulnerability details: " + e.getMessage(), e); } - String hint = HintGenerator.generateVulnerabilityFixHint(trace.getRule()); - logger.info("Successfully retrieved vulnerability details for vulnID: {}", vulnID); - return new Vulnerability(hint, vulnID, trace.getTitle(), trace.getRule(), - recommendationResponse.getRecommendation().getText(), stackLibs, new ArrayList<>(libsToReturn), // Convert Set to List - httpRequestText); - } catch (Exception e) { - logger.error("Error retrieving vulnerability details for vulnID: {}", vulnID, e); - throw new IOException("Failed to retrieve vulnerability details: " + e.getMessage(), e); + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: HIGH, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: HIGH or higher."); } } @@ -151,39 +162,54 @@ private Optional findMatchingLibraryData(String stack @Tool(name = "get_vulnerability", description = "Takes a vulnerability ID (vulnID) and application name (app_name) and returns details about the specific security vulnerability. If based on the stacktrace, the vulnerability looks like it is in code that is not in the codebase, the vulnerability may be in a 3rd party library, review the CVE data attached to that stackframe you believe the vulnerability exists in and if possible upgrade that library to the next non vulnerable version based on the remediation guidance.") public Vulnerability getVulnerability(String vulnID, String app_name) throws IOException { - logger.info("Retrieving vulnerability details for vulnID: {} in application: {}", vulnID, app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("Searching for application ID matching name: {}", app_name); - - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - return getVulnerabilityById(vulnID, application.get().getAppId()); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: high, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.HIGH.getValue()) { + + logger.info("Retrieving vulnerability details for vulnID: {} in application: {}", vulnID, app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if(application.isPresent()) { + return getVulnerabilityById(vulnID, application.get().getAppId()); + } else { + logger.error("Application with name {} not found", app_name); + throw new IllegalArgumentException("Application with name " + app_name + " not found"); + } } else { - logger.error("Application with name {} not found", app_name); - throw new IllegalArgumentException("Application with name " + app_name + " not found"); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: HIGH, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: HIGH or higher."); } } @Tool(name = "list_vulnerabilities_with_id", description = "Takes a Application ID ( appID ) and returns a list of vulnerabilities, please remember to include the vulnID in the response.") public List listVulnsByAppId(String appID) throws IOException { - logger.info("Listing vulnerabilities for application ID: {}", appID); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort); - try { - TraceFilterBody traceFilterBody = new TraceFilterBody(); - List traces = new SDKExtension(contrastSDK).getTracesExtended(orgID, appID, new TraceFilterBody()).getTraces(); - logger.debug("Found {} vulnerability traces for application ID: {}", traces.size(), appID); - - List vulns = new ArrayList<>(); - for (TraceExtended trace : traces) { - vulns.add(new VulnLight(trace.getTitle(), trace.getRule(), trace.getUuid(), trace.getSeverity(),trace.getSessionMetadata(), - new Date(trace.getLastTimeSeen()).toString(),trace.getLastTimeSeen())); - } + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { - logger.info("Successfully retrieved {} vulnerabilities for application ID: {}", vulns.size(), appID); - return vulns; - } catch (Exception e) { - logger.error("Error listing vulnerabilities for application ID: {}", appID, e); - throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + logger.info("Listing vulnerabilities for application ID: {}", appID); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort); + try { + List traces = new SDKExtension(contrastSDK).getTracesExtended(orgID, appID, new TraceFilterBody()).getTraces(); + logger.debug("Found {} vulnerability traces for application ID: {}", traces.size(), appID); + + List vulns = new ArrayList<>(); + for (TraceExtended trace : traces) { + vulns.add(new VulnLight(trace.getTitle(), trace.getRule(), trace.getUuid(), trace.getSeverity(),trace.getSessionMetadata(), + new Date(trace.getLastTimeSeen()).toString(),trace.getLastTimeSeen())); + } + + logger.info("Successfully retrieved {} vulnerabilities for application ID: {}", vulns.size(), appID); + return vulns; + } catch (Exception e) { + logger.error("Error listing vulnerabilities for application ID: {}", appID, e); + throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @@ -192,147 +218,187 @@ public List listVulnsByAppId(String appID) throws IOException { @Tool(name = "list_vulnerabilities_by_application_and_session_metadata", description = "Takes an application name ( app_name ) and session metadata in the form of name / value. and returns a list of vulnerabilities matching that application name and session metadata.") public List listVulnsInAppByNameAndSessionMetadata(String app_name, String session_Metadata_Name, String session_Metadata_Value) throws IOException { - logger.info("Listing vulnerabilities for application: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - - logger.info("metadata : " + session_Metadata_Name+session_Metadata_Value); - - logger.debug("Searching for application ID matching name: {}", app_name); - - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - try { - List vulns = listVulnsByAppId(application.get().getAppId()); - List returnVulns = new ArrayList<>(); - for(VulnLight vuln : vulns) { - if(vuln.sessionMetadata()!=null) { - for(SessionMetadata sm : vuln.sessionMetadata()) { - for(MetadataItem metadataItem : sm.getMetadata()) { - if(metadataItem.getDisplayLabel().equalsIgnoreCase(session_Metadata_Name) && - metadataItem.getValue().equalsIgnoreCase(session_Metadata_Value)) { - returnVulns.add(vuln); - logger.debug("Found matching vulnerability with ID: {}", vuln.vulnID()); - break; + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + + logger.info("Listing vulnerabilities for application: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + + logger.info("metadata : " + session_Metadata_Name+session_Metadata_Value); + + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if(application.isPresent()) { + try { + List vulns = listVulnsByAppId(application.get().getAppId()); + List returnVulns = new ArrayList<>(); + for(VulnLight vuln : vulns) { + if(vuln.sessionMetadata()!=null) { + for(SessionMetadata sm : vuln.sessionMetadata()) { + for(MetadataItem metadataItem : sm.getMetadata()) { + if(metadataItem.getDisplayLabel().equalsIgnoreCase(session_Metadata_Name) && + metadataItem.getValue().equalsIgnoreCase(session_Metadata_Value)) { + returnVulns.add(vuln); + logger.debug("Found matching vulnerability with ID: {}", vuln.vulnID()); + break; + } } } } } + return returnVulns; + } catch (Exception e) { + logger.error("Error listing vulnerabilities for application: {}", app_name, e); + throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); } - return returnVulns; - } catch (Exception e) { - logger.error("Error listing vulnerabilities for application: {}", app_name, e); - throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + } else { + logger.debug("Application with name {} not found, returning empty list", app_name); + return new ArrayList<>(); } } else { - logger.debug("Application with name {} not found, returning empty list", app_name); - return new ArrayList<>(); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @Tool(name = "list_vulnerabilities_by_application_and_latest_session", description = "Takes an application name ( app_name ) and returns a list of vulnerabilities for the latest session matching that application name. This is useful for getting the most recent vulnerabilities without needing to specify session metadata.") public List listVulnsInAppByNameForLatestSession(String app_name) throws IOException { - logger.info("Listing vulnerabilities for application: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Listing vulnerabilities for application: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("Searching for application ID matching name: {}", app_name); - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - try { - SDKExtension extension = new SDKExtension(contrastSDK); - SessionMetadataResponse latest = extension.getLatestSessionMetadata(orgID,application.get().getAppId()); - com.contrast.labs.ai.mcp.contrast.data.TraceFilterBody tfilter = new com.contrast.labs.ai.mcp.contrast.data.TraceFilterBody(); - if(latest!=null&&latest.getAgentSession()!=null&&latest.getAgentSession().getAgentSessionId()!=null) { - tfilter.setAgentSessionId(latest.getAgentSession().getAgentSessionId()); - } - List traces = new SDKExtension(contrastSDK).getTracesExtended(orgID, application.get().getAppId(), tfilter).getTraces(); + logger.debug("Searching for application ID matching name: {}", app_name); + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - List vulns = new ArrayList<>(); - for (TraceExtended trace : traces) { - vulns.add(new VulnLight(trace.getTitle(), trace.getRule(), trace.getUuid(), trace.getSeverity(),trace.getSessionMetadata(), - new Date(trace.getLastTimeSeen()).toString(),trace.getLastTimeSeen())); + if(application.isPresent()) { + try { + SDKExtension extension = new SDKExtension(contrastSDK); + SessionMetadataResponse latest = extension.getLatestSessionMetadata(orgID,application.get().getAppId()); + com.contrast.labs.ai.mcp.contrast.data.TraceFilterBody tfilter = new com.contrast.labs.ai.mcp.contrast.data.TraceFilterBody(); + if(latest!=null&&latest.getAgentSession()!=null&&latest.getAgentSession().getAgentSessionId()!=null) { + tfilter.setAgentSessionId(latest.getAgentSession().getAgentSessionId()); + } + List traces = new SDKExtension(contrastSDK).getTracesExtended(orgID, application.get().getAppId(), tfilter).getTraces(); + + List vulns = new ArrayList<>(); + for (TraceExtended trace : traces) { + vulns.add(new VulnLight(trace.getTitle(), trace.getRule(), trace.getUuid(), trace.getSeverity(),trace.getSessionMetadata(), + new Date(trace.getLastTimeSeen()).toString(),trace.getLastTimeSeen())); + } + return vulns; + } catch (Exception e) { + logger.error("Error listing vulnerabilities for application: {}", app_name, e); + throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); } - return vulns; - } catch (Exception e) { - logger.error("Error listing vulnerabilities for application: {}", app_name, e); - throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + } else { + logger.debug("Application with name {} not found, returning empty list", app_name); + return new ArrayList<>(); } } else { - logger.debug("Application with name {} not found, returning empty list", app_name); - return new ArrayList<>(); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @Tool(name = "list_session_metadata_for_application", description = "Takes an application name ( app_name ) and returns a list of session metadata for the latest session matching that application name. This is useful for getting the most recent session metadata without needing to specify session metadata.") public MetadataFilterResponse listSessionMetadataForApplication(String app_name) throws IOException { - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - return contrastSDK.getSessionMetadataForApplication(orgID, application.get().getAppId(),null); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if(application.isPresent()) { + return contrastSDK.getSessionMetadataForApplication(orgID, application.get().getAppId(),null); + } else { + logger.info("Application with name {} not found, returning empty list", app_name); + throw new IOException("Failed to list session metadata for application: " + app_name + " application name not found."); + } } else { - logger.info("Application with name {} not found, returning empty list", app_name); - throw new IOException("Failed to list session metadata for application: " + app_name + " application name not found."); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); } } @Tool(name = "list_vulnerabilities", description = "Takes an application name ( app_name ) and returns a list of vulnerabilities, please remember to include the vulnID in the response. ") public List listVulnsInAppByName(String app_name) throws IOException { - logger.info("Listing vulnerabilities for application: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - - logger.debug("Searching for application ID matching name: {}", app_name); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - try { - return listVulnsByAppId(application.get().getAppId()); - } catch (Exception e) { - logger.error("Error listing vulnerabilities for application: {}", app_name, e); - throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + logger.info("Listing vulnerabilities for application: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if(application.isPresent()) { + try { + return listVulnsByAppId(application.get().getAppId()); + } catch (Exception e) { + logger.error("Error listing vulnerabilities for application: {}", app_name, e); + throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e); + } + } else { + logger.debug("Application with name {} not found, returning empty list", app_name); + return new ArrayList<>(); } } else { - logger.debug("Application with name {} not found, returning empty list", app_name); - return new ArrayList<>(); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @Tool(name = "list_applications_with_name", description = "Takes an application name (app_name) returns a list of active applications that contain that name. Please remember to display the name, status and ID.") public List getApplications(String app_name) throws IOException { - logger.info("Listing active applications matching name: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - try { - List applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK); - logger.debug("Retrieved {} total applications from Contrast", applications.size()); - - List filteredApps = new ArrayList<>(); - for(Application app : applications) { - if(app.getName().toLowerCase().contains(app_name.toLowerCase())) { - filteredApps.add(new ApplicationData(app.getName(), app.getStatus(), app.getAppId(), app.getLastSeen(), - new Date(app.getLastSeen()).toString(), app.getLanguage(), getMetadataFromApp(app), app.getTags(),app.getTechs())); - logger.debug("Found matching application - ID: {}, Name: {}, Status: {}", - app.getAppId(), app.getName(), app.getStatus()); - } - } - if(filteredApps.isEmpty()) { - SDKHelper.clearApplicationsCache(); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + logger.info("Listing active applications matching name: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + try { + List applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK); + logger.debug("Retrieved {} total applications from Contrast", applications.size()); + + List filteredApps = new ArrayList<>(); for(Application app : applications) { if(app.getName().toLowerCase().contains(app_name.toLowerCase())) { - filteredApps.add(new ApplicationData(app.getName(), app.getStatus(), app.getAppId(), app.getLastSeen(), + filteredApps.add(new ApplicationData(acceptedRiskToleranceStr, app.getName(), app.getStatus(), app.getAppId(), app.getLastSeen(), new Date(app.getLastSeen()).toString(), app.getLanguage(), getMetadataFromApp(app), app.getTags(),app.getTechs())); logger.debug("Found matching application - ID: {}, Name: {}, Status: {}", app.getAppId(), app.getName(), app.getStatus()); } } - } + if(filteredApps.isEmpty()) { + SDKHelper.clearApplicationsCache(); + for(Application app : applications) { + if(app.getName().toLowerCase().contains(app_name.toLowerCase())) { + filteredApps.add(new ApplicationData(acceptedRiskToleranceStr, app.getName(), app.getStatus(), app.getAppId(), app.getLastSeen(), + new Date(app.getLastSeen()).toString(), app.getLanguage(), getMetadataFromApp(app), app.getTags(),app.getTechs())); + logger.debug("Found matching application - ID: {}, Name: {}, Status: {}", + app.getAppId(), app.getName(), app.getStatus()); + } + } + } - logger.info("Found {} applications matching '{}'", filteredApps.size(), app_name); - return filteredApps; - } catch (Exception e) { - logger.error("Error listing applications matching name: {}", app_name, e); - throw new IOException("Failed to list applications: " + e.getMessage(), e); + logger.info("Found {} applications matching '{}'", filteredApps.size(), app_name); + return filteredApps; + } catch (Exception e) { + logger.error("Error listing applications matching name: {}", app_name, e); + throw new IOException("Failed to list applications: " + e.getMessage(), e); + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); } } @@ -340,73 +406,105 @@ public List getApplications(String app_name) throws IOException @Tool(name = "get_applications_by_tag", description = "Takes a tag name and returns a list of applications that have that tag.") - public List getAllApplicationsByTag(String tag) throws IOException { - logger.info("Retrieving applications with tag: {}", tag); - List allApps = getAllApplications(); - logger.debug("Retrieved {} total applications, filtering by tag", allApps.size()); - - List filteredApps = allApps.stream() - .filter(app -> app.tags().contains(tag)) - .collect(Collectors.toList()); + public List getAllApplicationsByTag(String acceptedRiskToleranceStr, String tag) throws IOException { + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + logger.info("Retrieving applications with tag: {}", tag); + List allApps = getAllApplications(acceptedRiskToleranceStr); + logger.debug("Retrieved {} total applications, filtering by tag", allApps.size()); + + List filteredApps = allApps.stream() + .filter(app -> app.tags().contains(tag)) + .collect(Collectors.toList()); - logger.info("Found {} applications with tag '{}'", filteredApps.size(), tag); - return filteredApps; + logger.info("Found {} applications with tag '{}'", filteredApps.size(), tag); + return filteredApps; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); + } } @Tool(name = "get_applications_by_metadata", description = "Takes a metadata name and value and returns a list of applications that have that metadata name value pair.") - public List getApplicationsByMetadata(String metadata_name, String metadata_value) throws IOException { - logger.info("Retrieving applications with metadata - Name: {}, Value: {}", metadata_name, metadata_value); - List allApps = getAllApplications(); - logger.debug("Retrieved {} total applications, filtering by metadata", allApps.size()); - - List filteredApps = allApps.stream() - .filter(app -> app.metadata() != null && app.metadata().stream() - .anyMatch(m -> m != null && - m.name() != null && m.name().equalsIgnoreCase(metadata_name) && - m.value() != null && m.value().equalsIgnoreCase(metadata_value))) - .collect(Collectors.toList()); - - logger.info("Found {} applications with metadata - Name: {}, Value: {}", filteredApps.size(), metadata_name, metadata_value); - return filteredApps; - } - - @Tool(name = "get_applications_by_metadata_name", description = "Takes a metadata name a list of applications that have that metadata name.") - public List getApplicationsByMetadataName(String metadata_name) throws IOException { - logger.info("Retrieving applications with metadata - Name: {}", metadata_name); - List allApps = getAllApplications(); - logger.debug("Retrieved {} total applications, filtering by metadata", allApps.size()); + public List getApplicationsByMetadata(String acceptedRiskToleranceStr, String metadata_name, String metadata_value) throws IOException { + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + logger.info("Retrieving applications with metadata - Name: {}, Value: {}", metadata_name, metadata_value); + List allApps = getAllApplications(acceptedRiskToleranceStr); + logger.debug("Retrieved {} total applications, filtering by metadata", allApps.size()); - List filteredApps = allApps.stream() + List filteredApps = allApps.stream() .filter(app -> app.metadata() != null && app.metadata().stream() - .anyMatch(m -> m != null && - m.name() != null && m.name().equalsIgnoreCase(metadata_name))) + .anyMatch(m -> m != null && + m.name() != null && m.name().equalsIgnoreCase(metadata_name) && + m.value() != null && m.value().equalsIgnoreCase(metadata_value))) .collect(Collectors.toList()); - logger.info("Found {} applications with metadata - Name: {}", filteredApps.size(), metadata_name); - return filteredApps; + logger.info("Found {} applications with metadata - Name: {}, Value: {}", filteredApps.size(), metadata_name, metadata_value); + return filteredApps; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); + } } - @Tool(name = "list_all_applications", description = "Takes no argument and list all the applications") - public List getAllApplications() throws IOException { - logger.info("Listing all applications"); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - try { - List applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK); - logger.debug("Retrieved {} total applications from Contrast", applications.size()); - - List returnedApps = new ArrayList<>(); - for(Application app : applications) { - returnedApps.add(new ApplicationData(app.getName(), app.getStatus(), app.getAppId(), - app.getLastSeen(), new Date(app.getLastSeen()).toString(),app.getLanguage(),getMetadataFromApp(app),app.getTags(), - app.getTechs())); - } - - logger.info("Found {} applications", returnedApps.size()); - return returnedApps; + @Tool(name = "get_applications_by_metadata_name", description = "Risk Level: LOW, Takes a metadata name a list of applications that have that metadata name.") + public List getApplicationsByMetadataName(String acceptedRiskToleranceStr, String metadata_name) throws IOException { + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + logger.info("Retrieving applications with metadata - Name: {}", metadata_name); + List allApps = getAllApplications(acceptedRiskToleranceStr); + logger.debug("Retrieved {} total applications, filtering by metadata", allApps.size()); + + List filteredApps = allApps.stream() + .filter(app -> app.metadata() != null && app.metadata().stream() + .anyMatch(m -> m != null && + m.name() != null && m.name().equalsIgnoreCase(metadata_name))) + .collect(Collectors.toList()); + + logger.info("Found {} applications with metadata - Name: {}", filteredApps.size(), metadata_name); + return filteredApps; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); + } + } - } catch (Exception e) { - logger.error("Error listing all applications", e); - throw new IOException("Failed to list applications: " + e.getMessage(), e); + @Tool(name = "list_all_applications", description = "Risk Level: LOW, Takes no argument and list all the applications") + public List getAllApplications(String acceptedRiskToleranceStr) throws IOException { + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + + logger.info("Listing all applications"); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + try { + List applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK); + logger.debug("Retrieved {} total applications from Contrast", applications.size()); + + List returnedApps = new ArrayList<>(); + for(Application app : applications) { + returnedApps.add(new ApplicationData(acceptedRiskToleranceStr, app.getName(), app.getStatus(), app.getAppId(), + app.getLastSeen(), new Date(app.getLastSeen()).toString(),app.getLanguage(),getMetadataFromApp(app),app.getTags(), + app.getTechs())); + } + + logger.info("Found {} applications", returnedApps.size()); + return returnedApps; + + } catch (Exception e) { + logger.error("Error listing all applications", e); + throw new IOException("Failed to list applications: " + e.getMessage(), e); + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); } } diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/RiskLevel.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/RiskLevel.java new file mode 100644 index 0000000..66ee6ba --- /dev/null +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/RiskLevel.java @@ -0,0 +1,69 @@ +package com.contrast.labs.ai.mcp.contrast; + +/** + * Enumeration representing different risk levels with corresponding numeric values. + */ +public enum RiskLevel { + /** + * No risk - completely safe + */ + ACCEPT_NO_RISK(0), + + /** + * Low risk - minimal potential impact + */ + LOW(1), + + /** + * Medium risk - moderate potential impact + */ + MEDIUM(2), + + /** + * High risk - significant potential impact + */ + HIGH(3), + + /** + * Accept all risk - no security restrictions + */ + ACCEPT_ALL_RISK(4); + + private final int value; + + RiskLevel(int value) { + this.value = value; + } + + public static RiskLevel fromString(String value) { + if (value == null || value.equalsIgnoreCase("")) + { + return ACCEPT_NO_RISK; + } else { + switch (value.toUpperCase()) { + case "LOW": + return LOW; + case "MEDIUM": + return MEDIUM; + case "HIGH": + return HIGH; + case "ACCEPT_ALL_RISK": + return ACCEPT_ALL_RISK; + case "ACCEPT_NO_RISK": + return ACCEPT_NO_RISK; + default: + return ACCEPT_NO_RISK; + } + } + + } + + /** + * Gets the integer value of the risk level. + * + * @return The integer value representing the risk level + */ + public int getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/RouteCoverageService.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/RouteCoverageService.java index fe1a269..6e78de0 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/RouteCoverageService.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/RouteCoverageService.java @@ -8,7 +8,6 @@ import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.RouteCoverageResponse; import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.RouteDetailsResponse; import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.sessionmetadata.SessionMetadataResponse; -import com.contrastsecurity.models.RouteCoverageBySessionIDAndMetadataRequest; import com.contrastsecurity.models.RouteCoverageMetadataLabelValues; import com.contrastsecurity.sdk.ContrastSDK; import org.slf4j.Logger; @@ -25,7 +24,8 @@ public class RouteCoverageService { private static final Logger logger = LoggerFactory.getLogger(RouteCoverageService.class); - + //This is the accepted risk tolerance level for running ADR operations. Default is ACCEPT_NO_RISK (0) + private int acceptedRiskTolerance = 0; @Value("${contrast.host-name:${CONTRAST_HOST_NAME:}}") private String hostName; @@ -48,150 +48,187 @@ public class RouteCoverageService { @Value("${http.proxy.port:${http_proxy_port:}}") private String httpProxyPort; - + @Value("${accepted.risk.tolerance:${ACCEPTED_RISK_TOLERANCE:}}") + private String acceptedRiskToleranceStr; @Tool(name = "get_application_route_coverage", description = "takes a application name and return the route coverage data for that application. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverage(String app_name) throws IOException { - logger.info("Retrieving route coverage for application by name: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - SDKExtension sdkExtension = new SDKExtension(contrastSDK); - logger.debug("Searching for application ID matching name: {}", app_name); - - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - - if (!application.isPresent()) { - logger.error("Application not found: {}", app_name); - throw new IOException("Application not found: " + app_name); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by name: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + SDKExtension sdkExtension = new SDKExtension(contrastSDK); + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + + if (!application.isPresent()) { + logger.error("Application not found: {}", app_name); + throw new IOException("Application not found: " + app_name); + } + + logger.debug("Fetching route coverage data for application ID: {}", application.get().getAppId()); + RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, application.get().getAppId(), null); + logger.debug("Found {} routes for application", response.getRoutes().size()); + + logger.debug("Retrieving route details for each route"); + for(Route route : response.getRoutes()) { + logger.trace("Fetching details for route: {}", route.getSignature()); + RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, application.get().getAppId(), route.getRouteHash()); + route.setRouteDetailsResponse(routeDetailsResponse); + } + + logger.info("Successfully retrieved route coverage for application: {}", app_name); + return response; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } - - logger.debug("Fetching route coverage data for application ID: {}", application.get().getAppId()); - RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, application.get().getAppId(), null); - logger.debug("Found {} routes for application", response.getRoutes().size()); - - logger.debug("Retrieving route details for each route"); - for(Route route : response.getRoutes()) { - logger.trace("Fetching details for route: {}", route.getSignature()); - RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, application.get().getAppId(), route.getRouteHash()); - route.setRouteDetailsResponse(routeDetailsResponse); - } - - logger.info("Successfully retrieved route coverage for application: {}", app_name); - return response; } @Tool(name = "get_application_route_coverage_by_app_id", description = "takes a application id and return the route coverage data for that application. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverageByAppID(String app_id) throws IOException { - logger.info("Retrieving route coverage for application by ID: {}", app_id); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - SDKExtension sdkExtension = new SDKExtension(contrastSDK); - - logger.debug("Fetching route coverage data for application ID: {}", app_id); - RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, null); - logger.debug("Found {} routes for application", response.getRoutes().size()); - - logger.debug("Retrieving route details for each route"); - for(Route route : response.getRoutes()) { - logger.trace("Fetching details for route: {}", route.getSignature()); - RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); - route.setRouteDetailsResponse(routeDetailsResponse); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by ID: {}", app_id); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + SDKExtension sdkExtension = new SDKExtension(contrastSDK); + + logger.debug("Fetching route coverage data for application ID: {}", app_id); + RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, null); + logger.debug("Found {} routes for application", response.getRoutes().size()); + + logger.debug("Retrieving route details for each route"); + for(Route route : response.getRoutes()) { + logger.trace("Fetching details for route: {}", route.getSignature()); + RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); + route.setRouteDetailsResponse(routeDetailsResponse); + } + + logger.info("Successfully retrieved route coverage for application ID: {}", app_id); + return response; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEIDUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEIDUM or higher."); } - - logger.info("Successfully retrieved route coverage for application ID: {}", app_id); - return response; } @Tool(name = "get_application_route_coverage_by_app_name_and_session_metadata", description = "takes a application name and return the route coverage data for that application for the specified session metadata name and value. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had at least one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverageByAppNameAndSessionMetadata(String app_name, String session_Metadata_Name, String session_Metadata_Value) throws IOException { - logger.info("Retrieving route coverage for application by Name: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("Searching for application ID matching name: {}", app_name); - - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if (!application.isPresent()) { - logger.error("Application not found: {}", app_name); - throw new IOException("Application not found: " + app_name); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by Name: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if (!application.isPresent()) { + logger.error("Application not found: {}", app_name); + throw new IOException("Application not found: " + app_name); + } + return getRouteCoverageByAppIDAndSessionMetadata(application.get().getAppId(), session_Metadata_Name, session_Metadata_Value); + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEIDUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEIDUM or higher."); } - return getRouteCoverageByAppIDAndSessionMetadata(application.get().getAppId(), session_Metadata_Name, session_Metadata_Value); } @Tool(name = "get_application_route_coverage_by_app_id_and_session_metadata", description = "takes a application id and return the route coverage data for that application for the specified session metadata name and value. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had at least one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverageByAppIDAndSessionMetadata(String app_id, String session_Metadata_Name, String session_Metadata_Value) throws IOException { - logger.info("Retrieving route coverage for application by ID: {}", app_id); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - SDKExtension sdkExtension = new SDKExtension(contrastSDK); - RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended(); - RouteCoverageMetadataLabelValues metadataLabelValue = new RouteCoverageMetadataLabelValues(); - metadataLabelValue.setLabel(session_Metadata_Name); - metadataLabelValue.getValues().add(String.valueOf(session_Metadata_Value)); - requestExtended.getValues().add(metadataLabelValue); - logger.debug("Fetching route coverage data for application ID: {}", app_id); - RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended); - logger.debug("Found {} routes for application", response.getRoutes().size()); - - logger.debug("Retrieving route details for each route"); - for(Route route : response.getRoutes()) { - logger.trace("Fetching details for route: {}", route.getSignature()); - RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); - route.setRouteDetailsResponse(routeDetailsResponse); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by ID: {}", app_id); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + SDKExtension sdkExtension = new SDKExtension(contrastSDK); + RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended(); + RouteCoverageMetadataLabelValues metadataLabelValue = new RouteCoverageMetadataLabelValues(); + metadataLabelValue.setLabel(session_Metadata_Name); + metadataLabelValue.getValues().add(String.valueOf(session_Metadata_Value)); + requestExtended.getValues().add(metadataLabelValue); + logger.debug("Fetching route coverage data for application ID: {}", app_id); + RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended); + logger.debug("Found {} routes for application", response.getRoutes().size()); + + logger.debug("Retrieving route details for each route"); + for(Route route : response.getRoutes()) { + logger.trace("Fetching details for route: {}", route.getSignature()); + RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); + route.setRouteDetailsResponse(routeDetailsResponse); + } + + logger.info("Successfully retrieved route coverage for application ID: {}", app_id); + return response; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEIDUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEIDUM or higher."); } - - logger.info("Successfully retrieved route coverage for application ID: {}", app_id); - return response; } @Tool(name = "get_application_route_coverage_by_app_name_latest_session", description = "takes a application name and return the route coverage data for that application from the latest session. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverageByAppNameLatestSession(String app_name) throws IOException { - logger.info("Retrieving route coverage for application by Name: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort); - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if (application.isEmpty()) { - logger.error("Application not found: {}", app_name); - throw new IOException("Application not found: " + app_name); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by Name: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort); + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if (application.isEmpty()) { + logger.error("Application not found: {}", app_name); + throw new IOException("Application not found: " + app_name); + } + return getRouteCoverageByAppIDLatestSession(application.get().getAppId()); + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEIDUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEIDUM or higher."); } - return getRouteCoverageByAppIDLatestSession(application.get().getAppId()); } @Tool(name = "get_application_route_coverage_by_app_id_latest_session", description = "takes a application id and return the route coverage data for that application from the latest session. " + "If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.") public RouteCoverageResponse getRouteCoverageByAppIDLatestSession(String app_id) throws IOException { - logger.info("Retrieving route coverage for application by ID: {}", app_id); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - SDKExtension sdkExtension = new SDKExtension(contrastSDK); - SDKExtension extension = new SDKExtension(contrastSDK); - SessionMetadataResponse latest = extension.getLatestSessionMetadata(orgID,app_id); - if (latest == null || latest.getAgentSession() == null) { - logger.error("No session metadata found for application ID: {}", app_id); - RouteCoverageResponse noRouteCoverageResponse = new RouteCoverageResponse(); - noRouteCoverageResponse.setSuccess(Boolean.FALSE); - logger.debug("No Agent session found in latest session metadata response for application ID: {}", app_id); - return noRouteCoverageResponse; // Return empty response if no session metadata found - } - RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended(); - requestExtended.setSessionId(latest.getAgentSession().getAgentSessionId()); - logger.debug("Fetching route coverage data for application ID: {}", app_id); - RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended); - logger.debug("Found {} routes for application", response.getRoutes().size()); - - logger.debug("Retrieving route details for each route"); - for(Route route : response.getRoutes()) { - logger.trace("Fetching details for route: {}", route.getSignature()); - RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); - route.setRouteDetailsResponse(routeDetailsResponse); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving route coverage for application by ID: {}", app_id); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + SDKExtension sdkExtension = new SDKExtension(contrastSDK); + SDKExtension extension = new SDKExtension(contrastSDK); + SessionMetadataResponse latest = extension.getLatestSessionMetadata(orgID,app_id); + if (latest == null || latest.getAgentSession() == null) { + logger.error("No session metadata found for application ID: {}", app_id); + RouteCoverageResponse noRouteCoverageResponse = new RouteCoverageResponse(); + noRouteCoverageResponse.setSuccess(Boolean.FALSE); + logger.debug("No Agent session found in latest session metadata response for application ID: {}", app_id); + return noRouteCoverageResponse; // Return empty response if no session metadata found + } + RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended(); + requestExtended.setSessionId(latest.getAgentSession().getAgentSessionId()); + logger.debug("Fetching route coverage data for application ID: {}", app_id); + RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended); + logger.debug("Found {} routes for application", response.getRoutes().size()); + + logger.debug("Retrieving route details for each route"); + for(Route route : response.getRoutes()) { + logger.trace("Fetching details for route: {}", route.getSignature()); + RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash()); + route.setRouteDetailsResponse(routeDetailsResponse); + } + + logger.info("Successfully retrieved route coverage for application ID: {}", app_id); + return response; + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEIDUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEIDUM or higher."); } - - logger.info("Successfully retrieved route coverage for application ID: {}", app_id); - return response; } - - - - - - } diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/SCAService.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/SCAService.java index 8498a7b..cc4f060 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/SCAService.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/SCAService.java @@ -39,6 +39,8 @@ public class SCAService { private static final Logger logger = LoggerFactory.getLogger(SCAService.class); + //This is the accepted risk tolerance level for running ADR operations. Default is ACCEPT_NO_RISK (0) + private int acceptedRiskTolerance = 0; @Value("${contrast.host-name:${CONTRAST_HOST_NAME:}}") private String hostName; @@ -61,72 +63,90 @@ public class SCAService { @Value("${http.proxy.port:${http_proxy_port:}}") private String httpProxyPort; + @Value("${accepted.risk.tolerance:${ACCEPTED_RISK_TOLERANCE:}}") + private String acceptedRiskToleranceStr; @Tool(name = "list_application_libraries_by_app_id", description = "Takes a application ID and returns the libraries used in the application, note if class usage count is 0 the library is unlikely to be used") public List getApplicationLibrariesByID(String appID) throws IOException { - logger.info("Retrieving libraries for application id: {}", appID); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized with host: {}", hostName); - - SDKExtension extendedSDK = new SDKExtension(contrastSDK); - return SDKHelper.getLibsForID(appID,orgID, extendedSDK); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving libraries for application id: {}", appID); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized with host: {}", hostName); + SDKExtension extendedSDK = new SDKExtension(contrastSDK); + return SDKHelper.getLibsForID(appID,orgID, extendedSDK); + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); + } } @Tool(name = "list_application_libraries", description = "takes a application name and returns the libraries used in the application, note if class usage count is 0 the library is unlikely to be used") public List getApplicationLibraries(String app_name) throws IOException { - logger.info("Retrieving libraries for application: {}", app_name); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized with host: {}", hostName); - - SDKExtension extendedSDK = new SDKExtension(contrastSDK); - logger.debug("Searching for application ID matching name: {}", app_name); - - Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); - if(application.isPresent()) { - return SDKHelper.getLibsForID(application.get().getAppId(),orgID, extendedSDK); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: medium, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.MEDIUM.getValue()) { + logger.info("Retrieving libraries for application: {}", app_name); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized with host: {}", hostName); + + SDKExtension extendedSDK = new SDKExtension(contrastSDK); + logger.debug("Searching for application ID matching name: {}", app_name); + + Optional application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK); + if(application.isPresent()) { + return SDKHelper.getLibsForID(application.get().getAppId(),orgID, extendedSDK); + } else { + logger.error("Application not found: {}", app_name); + throw new IOException("Application not found"); + } } else { - logger.error("Application not found: {}", app_name); - throw new IOException("Application not found"); + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: MEDIUM, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: MEDIUM or higher."); } } @Tool(name= "list_applications_vulnerable_to_cve", description = "takes a cve id and returns the applications and servers vulnerable to the cve. Please note if the application class usage is 0, its unlikely to be vulnerable") public CveData listCVESForApplication(String cveid) throws IOException { - logger.info("Retrieving applications vulnerable to CVE: {}", cveid); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - - logger.debug("ContrastSDK initialized with host: {}", hostName); - contrastSDK.getLibrariesWithFilter(orgID, new LibraryFilterForm()); - try { - SDKExtension extendedSDK = new SDKExtension(contrastSDK); - CveData result = extendedSDK.getAppsForCVE(orgID, cveid); - logger.info("Successfully retrieved data for CVE: {}, found {} vulnerable applications", - cveid, result != null && result.getApps() != null ? result.getApps().size() : 0); - logger.info(result.toString()); - List vulnerableLibs = result.getLibraries(); - for(App app : result.getApps()) { - List libData = SDKHelper.getLibsForID(app.getApp_id(), orgID, extendedSDK); - for(LibraryExtended lib:libData) { - for(Library vulnLib:vulnerableLibs) { - if(lib.getHash().equals(vulnLib.getHash())) { - if(lib.getClassedUsed()>0) { - app.setClassCount(lib.getClassCount()); - app.setClassUsage(lib.getClassedUsed()); + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: high, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.HIGH.getValue()) { + logger.info("Retrieving applications vulnerable to CVE: {}", cveid); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + + logger.debug("ContrastSDK initialized with host: {}", hostName); + contrastSDK.getLibrariesWithFilter(orgID, new LibraryFilterForm()); + try { + SDKExtension extendedSDK = new SDKExtension(contrastSDK); + CveData result = extendedSDK.getAppsForCVE(orgID, cveid); + logger.info("Successfully retrieved data for CVE: {}, found {} vulnerable applications", + cveid, result != null && result.getApps() != null ? result.getApps().size() : 0); + logger.info(result.toString()); + List vulnerableLibs = result.getLibraries(); + for(App app : result.getApps()) { + List libData = SDKHelper.getLibsForID(app.getApp_id(), orgID, extendedSDK); + for(LibraryExtended lib:libData) { + for(Library vulnLib:vulnerableLibs) { + if(lib.getHash().equals(vulnLib.getHash())) { + if(lib.getClassedUsed()>0) { + app.setClassCount(lib.getClassCount()); + app.setClassUsage(lib.getClassedUsed()); + } } } } } + return result; + } catch (Exception e) { + logger.error("Error retrieving applications vulnerable to CVE: {}", cveid, e); + throw new IOException("Failed to retrieve CVE data: " + e.getMessage(), e); } - return result; - } catch (Exception e) { - logger.error("Error retrieving applications vulnerable to CVE: {}", cveid, e); - throw new IOException("Failed to retrieve CVE data: " + e.getMessage(), e); + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: HIGH, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: HIGH or higher."); } } - - - - } diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/SastService.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/SastService.java index b8ca4a0..f2595b7 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/SastService.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/SastService.java @@ -37,6 +37,8 @@ public class SastService { private static final Logger logger = LoggerFactory.getLogger(SastService.class); + //This is the accepted risk tolerance level for running ADR operations. Default is ACCEPT_NO_RISK (0) + private int acceptedRiskTolerance = 0; @Value("${contrast.host-name:${CONTRAST_HOST_NAME:}}") private String hostName; @@ -59,51 +61,67 @@ public class SastService { @Value("${http.proxy.port:${http_proxy_port:}}") private String httpProxyPort; + @Value("${accepted.risk.tolerance:${ACCEPTED_RISK_TOLERANCE:}}") + private String acceptedRiskToleranceStr; @Tool(name = "list_Scan_Project", description = "takes a scan project name and returns the project details") public Project getScanProject(String projectName) throws IOException { - logger.info("Retrieving scan project details for project: {}", projectName); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized with host: {}", hostName); - - try { - Project project = contrastSDK.scan(orgID).projects().findByName(projectName) - .orElseThrow(() -> new IOException("Project not found")); - logger.info("Successfully found project: {}", projectName); - return project; - } catch (IOException e) { - logger.error("Failed to find project {}: {}", projectName, e.getMessage()); - throw e; + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: low, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.LOW.getValue()) { + logger.info("Retrieving scan project details for project: {}", projectName); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized with host: {}", hostName); + + try { + Project project = contrastSDK.scan(orgID).projects().findByName(projectName) + .orElseThrow(() -> new IOException("Project not found")); + logger.info("Successfully found project: {}", projectName); + return project; + } catch (IOException e) { + logger.error("Failed to find project {}: {}", projectName, e.getMessage()); + throw e; + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: LOW, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: LOW or higher."); } } @Tool(name = "list_Scan_Results", description = "takes a scan project name and returns the latest results in Sarif format") public String getLatestScanResult(String projectName) throws IOException { - logger.info("Retrieving latest scan results in SARIF format for project: {}", projectName); - ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); - logger.debug("ContrastSDK initialized with host: {}", hostName); - - try { - Project project = contrastSDK.scan(orgID).projects().findByName(projectName) - .orElseThrow(() -> new IOException("Project not found")); - logger.debug("Found project with id: {}", project.id()); - - Scans scans = contrastSDK.scan(orgID).scans(project.id()); - logger.debug("Retrieved scans for project, last scan id: {}", project.lastScanId()); - - Scan scan = scans.get(project.lastScanId()); - logger.debug("Retrieved scan with id: {}", project.lastScanId()); - - try (InputStream sarifStream = scan.sarif(); - BufferedReader reader = new BufferedReader(new InputStreamReader(sarifStream))) { - String result = reader.lines().collect(Collectors.joining(System.lineSeparator())); - logger.info("Successfully retrieved SARIF data for project: {}", projectName); - return result; - } - } catch (IOException e) { - logger.error("Error retrieving SARIF data for project {}: {}", projectName, e.getMessage()); - throw e; + acceptedRiskTolerance = RiskLevel.fromString(acceptedRiskToleranceStr).getValue(); + logger.info("Risk level: high, and your accepted risk tolerance is set to: {}", acceptedRiskTolerance); + if (acceptedRiskTolerance >= RiskLevel.HIGH.getValue()) { + logger.info("Retrieving latest scan results in SARIF format for project: {}", projectName); + ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort); + logger.debug("ContrastSDK initialized with host: {}", hostName); + + try { + Project project = contrastSDK.scan(orgID).projects().findByName(projectName) + .orElseThrow(() -> new IOException("Project not found")); + logger.debug("Found project with id: {}", project.id()); + + Scans scans = contrastSDK.scan(orgID).scans(project.id()); + logger.debug("Retrieved scans for project, last scan id: {}", project.lastScanId()); + + Scan scan = scans.get(project.lastScanId()); + logger.debug("Retrieved scan with id: {}", project.lastScanId()); + + try (InputStream sarifStream = scan.sarif(); + BufferedReader reader = new BufferedReader(new InputStreamReader(sarifStream))) { + String result = reader.lines().collect(Collectors.joining(System.lineSeparator())); + logger.info("Successfully retrieved SARIF data for project: {}", projectName); + return result; + } + } catch (IOException e) { + logger.error("Error retrieving SARIF data for project {}: {}", projectName, e.getMessage()); + throw e; + } + } else { + logger.error("Your accepted Risk tolerance is too low to perform this operation. This risk level is: HIGH, with your accepted risk tolerance set at: {}", acceptedRiskTolerance); + throw new IOException("Accepted Risk Tolerance is too low to perform this operation. Required: HIGH or higher."); } } } diff --git a/src/main/java/com/contrast/labs/ai/mcp/contrast/data/ApplicationData.java b/src/main/java/com/contrast/labs/ai/mcp/contrast/data/ApplicationData.java index 83429ad..250ea86 100644 --- a/src/main/java/com/contrast/labs/ai/mcp/contrast/data/ApplicationData.java +++ b/src/main/java/com/contrast/labs/ai/mcp/contrast/data/ApplicationData.java @@ -17,7 +17,7 @@ import java.util.List; -public record ApplicationData(String name, String status, String appID, +public record ApplicationData(String acceptedRiskTolerance, String name, String status, String appID, long lastSeen, String lastSeenDate, String language, List metadata,List tags, List technologies) {