diff --git a/.gitignore b/.gitignore index 97c68c84f..7221e1240 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ kustomize/components/oidc-ca/*.crt +CLAUDE.md + # Ignore anything in the ./.tmp directory .tmp/ diff --git a/.tekton/on-cm-runner.yaml b/.tekton/on-cm-runner.yaml index 439d6114e..87a109717 100644 --- a/.tekton/on-cm-runner.yaml +++ b/.tekton/on-cm-runner.yaml @@ -26,7 +26,7 @@ spec: value: "{{ trigger_comment }}" # Point to the image ALREADY built by the PR pipeline - name: target-image - value: quay.io/ecosystem-appeng/agent-morpheus-rh:on-pr-{{revision}} + value: quay.io/ecosystem-appeng/exploit-iq-agent:on-pr-{{revision}} pipelineSpec: params: diff --git a/.tekton/on-pull-request.yaml b/.tekton/on-pull-request.yaml index e72258311..d2e3b8340 100644 --- a/.tekton/on-pull-request.yaml +++ b/.tekton/on-pull-request.yaml @@ -33,7 +33,7 @@ spec: - name: image-expires-after value: 5d - name: output-image - value: quay.io/ecosystem-appeng/agent-morpheus-rh:on-pr-{{revision}} + value: quay.io/ecosystem-appeng/exploit-iq-agent:on-pr-{{revision}} - name: path-context value: . - name: dockerfile diff --git a/.tekton/on-push.yaml b/.tekton/on-push.yaml index 5da2cc106..71316d17f 100644 --- a/.tekton/on-push.yaml +++ b/.tekton/on-push.yaml @@ -26,7 +26,7 @@ spec: - name: revision value: "{{ revision }}" - name: output-image - value: quay.io/ecosystem-appeng/agent-morpheus-rh:latest + value: quay.io/ecosystem-appeng/exploit-iq-agent:latest - name: path-context value: . - name: dockerfile diff --git a/.tekton/on-tag.yaml b/.tekton/on-tag.yaml index 08718fd32..c8721f1c1 100644 --- a/.tekton/on-tag.yaml +++ b/.tekton/on-tag.yaml @@ -26,7 +26,7 @@ spec: - name: revision value: "{{ revision }}" - name: output-image - value: 'quay.io/ecosystem-appeng/agent-morpheus-rh' + value: 'quay.io/ecosystem-appeng/exploit-iq-agent' - name: tag-name value: "{{ target_branch }}" - name: path-context diff --git a/ci/scripts/copyright.py b/ci/scripts/copyright.py index 56e7b7ab1..3a49e62a4 100755 --- a/ci/scripts/copyright.py +++ b/ci/scripts/copyright.py @@ -285,7 +285,7 @@ def _main(): repo, this script will just look for uncommitted files and in case of CI it compares between branches "$PR_TARGET_BRANCH" and "current-pr-branch" """ - log_level = logging.getLevelName(os.environ.get("MORPHEUS_LOG_LEVEL", "INFO")) + log_level = logging.getLevelName(os.environ.get("EXPLOIT_IQ_LOG_LEVEL", "INFO")) logging.basicConfig(format="%(levelname)s:%(message)s", level=log_level) ret_val = 0 diff --git a/ci/scripts/gitutils.py b/ci/scripts/gitutils.py index edfb44056..7415af369 100755 --- a/ci/scripts/gitutils.py +++ b/ci/scripts/gitutils.py @@ -632,7 +632,7 @@ def _parse_args(): def _main(): - log_level = logging.getLevelName(os.environ.get("MORPHEUS_LOG_LEVEL", "INFO")) + log_level = logging.getLevelName(os.environ.get("EXPLOIT_IQ_LOG_LEVEL", "INFO")) logging.basicConfig(format="%(levelname)s:%(message)s", level=log_level) args = _parse_args() diff --git a/exploit-iq-models b/exploit-iq-models new file mode 160000 index 000000000..43de21f51 --- /dev/null +++ b/exploit-iq-models @@ -0,0 +1 @@ +Subproject commit 43de21f513538ef8580227584a6e609fe8e5029a diff --git a/kustomize/README.md b/kustomize/README.md index 373017ef7..f7a23180e 100644 --- a/kustomize/README.md +++ b/kustomize/README.md @@ -16,7 +16,7 @@ limitations under the License. --> -# Deploying Exploit Intelligence on OpenShift Container Platform +# Deploying ExploitIQ on OpenShift Container Platform ## Prerequisites @@ -118,15 +118,14 @@ EOF ``` ### Step 5. Configure OAuth Credentials - -Exploit Intelligence uses OpenShift OAuth for user authentication. The OAuth client secret must be at least 32 bytes (256 bits) because Exploit Intelligence uses it to sign internal session tokens with HS256, which requires a minimum key length of 256 bits. +ExploitIQ uses OpenShift OAuth for user authentication. The OAuth client secret must be at least 32 bytes (256 bits) because ExploitIQ uses it to sign internal session tokens with HS256, which requires a minimum key length of 256 bits. > [!IMPORTANT] > Save the value of `$OAUTH_CLIENT_SECRET` after running the commands below. You need it after deployment to create or update the `OAuthClient` resource. #### First-Time Deployment -Use this procedure only if no `OAuthClient` named `exploit-iq-client` exists on the cluster. If another Exploit Intelligence installation already uses that `OAuthClient`, you must use the [Reusing an Existing OAuthClient](#reusing-an-existing-oauthclient) procedure instead — generating a new secret overwrites the existing one and breaks authentication for all users of that installation. +Use this procedure only if no `OAuthClient` named `exploit-iq-client` exists on the cluster. If another ExploitIQ installation already uses that `OAuthClient`, you must use the [Reusing an Existing OAuthClient](#reusing-an-existing-oauthclient) procedure instead — generating a new secret overwrites the existing one and breaks authentication for all users of that installation. Verify that the `OAuthClient` does not exist before proceeding: @@ -186,7 +185,7 @@ find . -type f -name 'exploit-iq-config.yml' -exec sed -i "s|CALLBACK_URL_PLACEH ## Selecting a Deployment Variant -Exploit Intelligence supports the following deployment variants. Run only one deployment command in the next section. +ExploitIQ supports the following deployment variants. Run only one deployment command in the next section. | Variant | Overlay | LLM | Use When | | --- | --- | --- | --- | @@ -196,7 +195,7 @@ Exploit Intelligence supports the following deployment variants. Run only one de --- -## Deploying Exploit Intelligence +## Deploying ExploitIQ ### Deploy with a Self-Hosted LLM @@ -215,7 +214,7 @@ sed -i "s/REPLACE_NAMESPACE/$YOUR_NAMESPACE_NAME/" overlays/mlops/grafana/kustom sed -i "s/REPLACE_NAMESPACE/$YOUR_NAMESPACE_NAME/" overlays/mlops/tempo/kustomization.yaml ``` -Create the Grafana token secret. Retrieve the token value from the Bitwarden vault entry **Exploit Intelligence Grafana SA Token**: +Create the Grafana token secret. Retrieve the token value from the Bitwarden vault entry **ExploitIQ Grafana SA Token**: ```shell oc create secret generic grafana-bearer-token \ @@ -256,7 +255,7 @@ oc kustomize overlays/remote-nim-all | oc apply -f - -n $YOUR_NAMESPACE_NAME ### Configure OpenShift OAuth > [!WARNING] -> Complete this step before attempting to log in to the Exploit Intelligence UI. Authentication fails if the `OAuthClient` resource is not configured correctly. +> Complete this step before attempting to log in to the ExploitIQ UI. Authentication fails if the `OAuthClient` resource is not configured correctly. After the deployment completes and the `exploit-iq-client` route is available, configure the OpenShift OAuth client. Select the procedure that matches your situation. @@ -291,9 +290,9 @@ oc patch oauthclient exploit-iq-client \ -p '{"redirectURIs":["http://exploit-iq-client:8080","'$HTTP_ROUTE'","'$HTTPS_ROUTE'"]}' ``` -### Grant Users Access to the Exploit Intelligence UI +### Grant Users Access to the ExploitIQ UI -Access to the Exploit Intelligence UI is controlled by OpenShift group membership. Add users to the `exploit-iq-view` group to grant UI access. Create the group if it does not exist: +Access to the ExploitIQ UI is controlled by OpenShift group membership. Add users to the `exploit-iq-view` group to grant UI access. Create the group if it does not exist: ```shell oc adm groups new exploit-iq-view @@ -458,7 +457,7 @@ oc kustomize overlays/ | oc apply -f - -n $YOUR_NAMESPACE_NAME --- -## Uninstalling Exploit Intelligence +## Uninstalling ExploitIQ Set your deployment variant and run one of the following commands: @@ -482,9 +481,9 @@ kustomize build overlays/$DEPLOYMENT_VARIANT_NAME/ | oc delete -f - --- -## Running Exploit Intelligence Locally +## Running ExploitIQ Locally -You can run Exploit Intelligence on a local machine without GPU hardware, for development, debugging, and troubleshooting. +You can run ExploitIQ on a local machine without GPU hardware, for development, debugging, and troubleshooting. Before you begin, install the following tools and verify that all binaries are available on your system path: @@ -567,7 +566,7 @@ The test variant uses encrypted secret files. To decrypt them, you need the foll - [GnuPG](https://www.gnupg.org/download/) - [SOPS](https://github.com/getsops/sops/releases) -- The private decryption key from the Bitwarden vault entry **Exploit Intelligence Tests Deployment Variant Private Key for Decryption** +- The private decryption key from the Bitwarden vault entry **ExploitIQ Tests Deployment Variant Private Key for Decryption** ### Deploying the Test Overlay @@ -717,7 +716,7 @@ helm upgrade --install \ --set llama3_1_70b_instruct_4bit.readinessProbe.periodSeconds=10 \ --set global.tolerationsKey=p4d-gpu \ --set nim-embed.ngcSecret.apiKey= \ - exploit-iq-tests ../../../exploit-iq-models/agent-morpheus-models + exploit-iq-tests ../exploit-iq-models/exploit-iq-models ``` **11.** Remove the decrypted secret files: @@ -737,4 +736,4 @@ oc delete project $(oc project --short -q) If you need to install the OpenShift Pipelines Operator on a new cluster, refer to the [OpenShift Pipelines installation documentation](https://docs.redhat.com/en/documentation/red_hat_openshift_pipelines/1.19/html/installing_and_configuring/installing-pipelines). -To configure the [Exploit Intelligence PAC GitHub application](https://github.com/apps/exploit-iq-pac/) on a new cluster, follow the [PAC GitHub application configuration guide](https://pipelinesascode.com/docs/install/github_apps/#configure-pipelines-as-code-on-your-cluster-to-access-the-github-app). You need the GitHub application private key and the webhook secret from the application settings. +To configure the [ExploitIQ PAC GitHub application](https://github.com/apps/exploit-iq-pac/) on a new cluster, follow the [PAC GitHub application configuration guide](https://pipelinesascode.com/docs/install/github_apps/#configure-pipelines-as-code-on-your-cluster-to-access-the-github-app). You need the GitHub application private key and the webhook secret from the application settings. diff --git a/kustomize/base/argilla/argilla-service.yaml b/kustomize/base/argilla/argilla-service.yaml index cc9f2840b..3c7f7d6ed 100644 --- a/kustomize/base/argilla/argilla-service.yaml +++ b/kustomize/base/argilla/argilla-service.yaml @@ -6,7 +6,7 @@ metadata: app: argilla spec: selector: - app: morpheus-feedback-api + app: exploit-iq-feedback-api ports: - protocol: TCP port: 6900 diff --git a/kustomize/base/argilla/argilla-user-feedback-pvc.yaml b/kustomize/base/argilla/argilla-user-feedback-pvc.yaml index 8a730ef7c..56d4479b8 100644 --- a/kustomize/base/argilla/argilla-user-feedback-pvc.yaml +++ b/kustomize/base/argilla/argilla-user-feedback-pvc.yaml @@ -4,7 +4,7 @@ kind: PersistentVolumeClaim metadata: name: argilla-user-feedback-pvc labels: - app: morpheus-feedback-api + app: exploit-iq-feedback-api spec: accessModes: - ReadWriteOnce diff --git a/kustomize/base/argilla/deployment.yaml b/kustomize/base/argilla/deployment.yaml index 4854af9ad..afb3aedca 100644 --- a/kustomize/base/argilla/deployment.yaml +++ b/kustomize/base/argilla/deployment.yaml @@ -1,20 +1,20 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: morpheus-feedback-api + name: exploit-iq-feedback-api labels: - app: morpheus-feedback-api + app: exploit-iq-feedback-api spec: replicas: 1 selector: matchLabels: - app: morpheus-feedback-api + app: exploit-iq-feedback-api strategy: type: Recreate template: metadata: labels: - app: morpheus-feedback-api + app: exploit-iq-feedback-api spec: restartPolicy: Always serviceAccountName: argilla diff --git a/kustomize/base/argilla/service.yaml b/kustomize/base/argilla/service.yaml index 545316330..a85c00bdf 100644 --- a/kustomize/base/argilla/service.yaml +++ b/kustomize/base/argilla/service.yaml @@ -1,12 +1,12 @@ apiVersion: v1 kind: Service metadata: - name: morpheus-feedback-api + name: exploit-iq-feedback-api labels: - app: morpheus-feedback-api + app: exploit-iq-feedback-api spec: selector: - app: morpheus-feedback-api + app: exploit-iq-feedback-api ports: - protocol: TCP port: 5001 diff --git a/kustomize/base/exploit_iq_client.yaml b/kustomize/base/exploit_iq_client.yaml index 7829e0e90..d8c0072dd 100644 --- a/kustomize/base/exploit_iq_client.yaml +++ b/kustomize/base/exploit_iq_client.yaml @@ -26,15 +26,15 @@ spec: args: - ./application - -Dquarkus.http.host=0.0.0.0 - - -Dquarkus.log.category."com.redhat.ecosystemappeng.morpheus".level=DEBUG - image: quay.io/ecosystem-appeng/agent-morpheus-client:latest + - -Dquarkus.log.category."com.redhat.ecosystemappeng.exploitiq".level=DEBUG + image: quay.io/ecosystem-appeng/exploit-iq-client:latest imagePullPolicy: Always ports: - name: http protocol: TCP containerPort: 8080 env: - - name: QUARKUS_REST-CLIENT_MORPHEUS_URL + - name: QUARKUS_REST-CLIENT_EXPLOIT_IQ_URL value: http://nginx-cache:8080/generate - name: QUARKUS_MONGODB_HOSTS value: exploit-iq-client-db:27017 @@ -78,9 +78,9 @@ spec: value: disabled - name: QUARKUS_HTTP_SSL_CERTIFICATE_RELOAD-PERIOD value: 30m - - name: MORPHEUS_UI_INCLUDES_PATH + - name: EXPLOIT_IQ_UI_INCLUDES_PATH value: /config/includes.json - - name: MORPHEUS_UI_EXCLUDES_PATH + - name: EXPLOIT_IQ_UI_EXCLUDES_PATH value: /config/excludes.json - name: DOCKER_CONFIG value: /tmp/.docker diff --git a/kustomize/base/exploit_iq_service.yaml b/kustomize/base/exploit_iq_service.yaml index 2f99c7411..01dbbfcd8 100644 --- a/kustomize/base/exploit_iq_service.yaml +++ b/kustomize/base/exploit_iq_service.yaml @@ -25,7 +25,7 @@ spec: serviceAccountName: exploit-iq-sa containers: - name: exploit-iq-phoenix-tracing - image: quay.io/ecosystem-appeng/agent-morpheus-rh:nat + image: quay.io/ecosystem-appeng/exploit-iq-agent:latest imagePullPolicy: Always workingDir: /workspace/ args: @@ -45,7 +45,7 @@ spec: memory: "1Gi" cpu: "100m" - name: exploit-iq - image: quay.io/ecosystem-appeng/agent-morpheus-rh:nat + image: quay.io/ecosystem-appeng/exploit-iq-agent:latest imagePullPolicy: Always workingDir: /workspace/ args: diff --git a/kustomize/base/kustomization.yaml b/kustomize/base/kustomization.yaml index 26f9fc41a..be4dfa5c5 100644 --- a/kustomize/base/kustomization.yaml +++ b/kustomize/base/kustomization.yaml @@ -80,10 +80,10 @@ configMapGenerator: options: disableNameSuffixHash: true images: - - name: quay.io/ecosystem-appeng/agent-morpheus-rh + - name: quay.io/ecosystem-appeng/exploit-iq-agent newTag: latest - - name: quay.io/ecosystem-appeng/agent-morpheus-client + - name: quay.io/ecosystem-appeng/exploit-iq-client newTag: latest - name: quay.io/ecosystem-appeng/exploitiq-mcp-server diff --git a/kustomize/deployer-rbac.yaml b/kustomize/deployer-rbac.yaml index 8be695176..863d37c33 100644 --- a/kustomize/deployer-rbac.yaml +++ b/kustomize/deployer-rbac.yaml @@ -1,7 +1,7 @@ # deployer-rbac.yaml # # Grants a non-cluster-admin user the minimum permissions required to -# deploy Exploit Intelligence on OpenShift Container Platform. +# deploy ExploitIQ on OpenShift Container Platform. # # Please replace the following placeholders: # — the OpenShift username of the deployer (e.g. jdoe) @@ -12,7 +12,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: exploit-intelligence-oauthclient-deploy + name: exploit-iq-oauthclient-deploy rules: # get and patch scoped to the two project OAuthClients only. - apiGroups: @@ -36,11 +36,11 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: exploit-intelligence-oauthclient-deploy + name: exploit-iq-oauthclient-deploy roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: exploit-intelligence-oauthclient-deploy + name: exploit-iq-oauthclient-deploy subjects: - kind: User name: @@ -48,7 +48,7 @@ subjects: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: exploit-intelligence-rbac-deploy + name: exploit-iq-rbac-deploy namespace: rules: - apiGroups: @@ -90,12 +90,12 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: exploit-intelligence-rbac-deploy + name: exploit-iq-rbac-deploy namespace: roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: exploit-intelligence-rbac-deploy + name: exploit-iq-rbac-deploy subjects: - kind: User name: diff --git a/kustomize/network-policy.yaml b/kustomize/network-policy.yaml index 5ea2ba756..3e16c3b0d 100644 --- a/kustomize/network-policy.yaml +++ b/kustomize/network-policy.yaml @@ -3,14 +3,14 @@ kind: NetworkPolicy metadata: labels: networking.knative.dev/ingress-provider: istio - name: allow-from-agent-morpheus-namespaces - namespace: morpheus-cn-models-nim + name: allow-from-exploit-iq-namespaces + namespace: exploit-iq-cn-models-nim spec: ingress: - from: - namespaceSelector: matchLabels: - application: agent-morpheus + application: exploit-iq podSelector: {} policyTypes: - Ingress diff --git a/kustomize/overlays/batch-processing/exploit-iq-client-batch-patch.yaml b/kustomize/overlays/batch-processing/exploit-iq-client-batch-patch.yaml index a753910a2..6391a08cc 100644 --- a/kustomize/overlays/batch-processing/exploit-iq-client-batch-patch.yaml +++ b/kustomize/overlays/batch-processing/exploit-iq-client-batch-patch.yaml @@ -25,7 +25,7 @@ spec: - name: exploit-iq-client imagePullPolicy: Always env: - - name: MORPHEUS_QUEUE_TIMEOUT + - name: EXPLOIT_IQ_QUEUE_TIMEOUT value: 60m - - name: MORPHEUS_QUEUE_MAX_ACTIVE + - name: EXPLOIT_IQ_QUEUE_MAX_ACTIVE value: "5" diff --git a/nginx/templates/variables/template-variables.conf.template b/nginx/templates/variables/template-variables.conf.template index e2f122c52..ed6656889 100644 --- a/nginx/templates/variables/template-variables.conf.template +++ b/nginx/templates/variables/template-variables.conf.template @@ -1,31 +1,31 @@ map $http_authorization $nemo_http_authorization { - 'Bearer AGENT_MORPHEUS' 'Bearer ${NGC_API_KEY}'; - 'Bearer "AGENT_MORPHEUS"' 'Bearer ${NGC_API_KEY}'; + 'Bearer EXPLOIT_IQ' 'Bearer ${NGC_API_KEY}'; + 'Bearer "EXPLOIT_IQ"' 'Bearer ${NGC_API_KEY}'; 'Bearer CYBER_DEV_DAY' 'Bearer ${NGC_API_KEY}'; 'Bearer "CYBER_DEV_DAY"' 'Bearer ${NGC_API_KEY}'; default $http_authorization; } map $http_organization_id $nemo_http_organization_id { - 'AGENT_MORPHEUS' '${NGC_ORG_ID}'; - '"AGENT_MORPHEUS"' '${NGC_ORG_ID}'; + 'EXPLOIT_IQ' '${NGC_ORG_ID}'; + '"EXPLOIT_IQ"' '${NGC_ORG_ID}'; 'CYBER_DEV_DAY' '${NGC_ORG_ID}'; '"CYBER_DEV_DAY"' '${NGC_ORG_ID}'; default $http_organization_id; } map $http_authorization $nim_http_authorization { - 'Bearer AGENT_MORPHEUS' 'Bearer ${NVIDIA_API_KEY}'; - 'Bearer "AGENT_MORPHEUS"' 'Bearer ${NVIDIA_API_KEY}'; + 'Bearer EXPLOIT_IQ' 'Bearer ${NVIDIA_API_KEY}'; + 'Bearer "EXPLOIT_IQ"' 'Bearer ${NVIDIA_API_KEY}'; 'Bearer CYBER_DEV_DAY' '${NVIDIA_API_KEY}'; 'Bearer "CYBER_DEV_DAY"' '${NVIDIA_API_KEY}'; default $http_authorization; } map $http_authorization $nvai_http_authorization { - 'Bearer AGENT_MORPHEUS' 'Bearer ${NVIDIA_API_KEY}'; - 'Bearer nvapi-AGENT_MORPHEUS' 'Bearer ${NVIDIA_API_KEY}'; - 'Bearer "nvapi-AGENT_MORPHEUS"' 'Bearer ${NVIDIA_API_KEY}'; + 'Bearer EXPLOIT_IQ' 'Bearer ${NVIDIA_API_KEY}'; + 'Bearer nvapi-EXPLOIT_IQ' 'Bearer ${NVIDIA_API_KEY}'; + 'Bearer "nvapi-EXPLOIT_IQ"' 'Bearer ${NVIDIA_API_KEY}'; 'Bearer CYBER_DEV_DAY' 'Bearer ${NVIDIA_API_KEY}'; 'Bearer nvapi-CYBER_DEV_DAY' 'Bearer ${NVIDIA_API_KEY}'; 'Bearer "nvapi-CYBER_DEV_DAY"' 'Bearer ${NVIDIA_API_KEY}'; @@ -33,22 +33,22 @@ map $http_authorization $nvai_http_authorization { } map $http_authorization $openai_http_authorization { - 'Bearer AGENT_MORPHEUS' 'Bearer ${OPENAI_API_KEY}'; - 'Bearer "AGENT_MORPHEUS"' 'Bearer ${OPENAI_API_KEY}'; + 'Bearer EXPLOIT_IQ' 'Bearer ${OPENAI_API_KEY}'; + 'Bearer "EXPLOIT_IQ"' 'Bearer ${OPENAI_API_KEY}'; 'Bearer CYBER_DEV_DAY' 'Bearer ${OPENAI_API_KEY}'; 'Bearer "CYBER_DEV_DAY"' 'Bearer ${OPENAI_API_KEY}'; default $http_authorization; } map $http_authorization $ghsa_http_authorization { - 'Bearer AGENT_MORPHEUS' 'Bearer ${GHSA_API_KEY}'; - 'Bearer "AGENT_MORPHEUS"' 'Bearer ${GHSA_API_KEY}'; + 'Bearer EXPLOIT_IQ' 'Bearer ${GHSA_API_KEY}'; + 'Bearer "EXPLOIT_IQ"' 'Bearer ${GHSA_API_KEY}'; 'Bearer CYBER_DEV_DAY' 'Bearer ${GHSA_API_KEY}'; 'Bearer "CYBER_DEV_DAY"' 'Bearer ${GHSA_API_KEY}'; default $http_authorization; } map $http_apikey $nvd_http_api_key { - 'AGENT_MORPHEUS' '${NVD_API_KEY}'; + 'EXPLOIT_IQ' '${NVD_API_KEY}'; default $http_apikey; } diff --git a/src/exploit_iq_commons/data_models/checker_status.py b/src/exploit_iq_commons/data_models/checker_status.py index 4047d02ea..fd4dccc3f 100644 --- a/src/exploit_iq_commons/data_models/checker_status.py +++ b/src/exploit_iq_commons/data_models/checker_status.py @@ -298,7 +298,7 @@ class L2BuildResult(BaseModel): class PackageCheckerContext(BaseModel): - """Consolidates all checker-specific state on AgentMorpheusInfo.""" + """Consolidates all checker-specific state on ExploitIqInfo.""" status: PackageCheckerStatus | None = None source_key: str | None = None artifacts: AcquiredArtifacts = Field(default_factory=AcquiredArtifacts) diff --git a/src/exploit_iq_commons/data_models/info.py b/src/exploit_iq_commons/data_models/info.py index 4f7bd1ef1..09d9f9876 100644 --- a/src/exploit_iq_commons/data_models/info.py +++ b/src/exploit_iq_commons/data_models/info.py @@ -30,9 +30,9 @@ class SBOMPackage(BaseModel): system: str -class AgentMorpheusInfo(BaseModel): +class ExploitIqInfo(BaseModel): """ - Information used for decisioning in the Agent Morpheus engine. These information can all be automatically + Information used for decisioning in the ExploitIQ engine. These information can all be automatically generated or retrieved by the pipeline from the input information. - vdb: paths to source code and documentation vector databases (VDBs) used to understand whether a vulnerability diff --git a/src/exploit_iq_commons/data_models/input.py b/src/exploit_iq_commons/data_models/input.py index b15e2f09a..720f5bc54 100644 --- a/src/exploit_iq_commons/data_models/input.py +++ b/src/exploit_iq_commons/data_models/input.py @@ -34,7 +34,7 @@ from exploit_iq_commons.data_models.common import HashableModel from exploit_iq_commons.data_models.common import PipelineMode , TargetPackage from exploit_iq_commons.data_models.common import TypedBaseModel -from exploit_iq_commons.data_models.info import AgentMorpheusInfo +from exploit_iq_commons.data_models.info import ExploitIqInfo from exploit_iq_commons.data_models.info import SBOMPackage DEFAULT_FAILURE_REASON = "No failure reason provided" @@ -206,9 +206,9 @@ def check_conflicting_refs(cls, source_info: list[SourceDocumentsInfo]) -> list[ return source_info -class AgentMorpheusInput(HashableModel): +class ExploitIqInput(HashableModel): """ - Inputs required by the Agent Morpheus pipeline. + Inputs required by the ExploitIQ pipeline. """ scan: ScanInfoInput image: ImageInfoInput @@ -220,12 +220,12 @@ class AgentMorpheusInput(HashableModel): failure_reason: str | None = Field(default=DEFAULT_FAILURE_REASON) -class AgentMorpheusEngineInput(BaseModel): +class ExploitIqEngineInput(BaseModel): """ - Inputs required by the Agent Morpheus Engine. + Inputs required by the ExploitIQ Engine. - - input: AgentMorpheusInput object that must be provided to the pipeline. - - info: AgentMorpheusInfo object that is retrieved/generated by the pipeline. + - input: ExploitIqInput object that must be provided to the pipeline. + - info: ExploitIqInfo object that is retrieved/generated by the pipeline. """ - input: AgentMorpheusInput - info: AgentMorpheusInfo + input: ExploitIqInput + info: ExploitIqInfo diff --git a/src/exploit_iq_commons/utils/chain_of_calls_retriever.py b/src/exploit_iq_commons/utils/chain_of_calls_retriever.py index 7f61f5a49..3cabd3b05 100644 --- a/src/exploit_iq_commons/utils/chain_of_calls_retriever.py +++ b/src/exploit_iq_commons/utils/chain_of_calls_retriever.py @@ -31,7 +31,7 @@ ) from exploit_iq_commons.utils.standard_library_cache import StandardLibraryCache -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") @dataclass diff --git a/src/exploit_iq_commons/utils/chain_of_calls_retriever_base.py b/src/exploit_iq_commons/utils/chain_of_calls_retriever_base.py index 9e9555177..9167f9c61 100644 --- a/src/exploit_iq_commons/utils/chain_of_calls_retriever_base.py +++ b/src/exploit_iq_commons/utils/chain_of_calls_retriever_base.py @@ -30,7 +30,7 @@ from exploit_iq_commons.logging.loggers_factory import LoggingFactory -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") def calculate_hashable_string_for_function(function_file_name: str, function_name_to_search: str) -> str: return f"{function_file_name};{function_name_to_search}" diff --git a/src/exploit_iq_commons/utils/dep_tree.py b/src/exploit_iq_commons/utils/dep_tree.py index e98ed0dc2..ce915ea88 100644 --- a/src/exploit_iq_commons/utils/dep_tree.py +++ b/src/exploit_iq_commons/utils/dep_tree.py @@ -75,7 +75,7 @@ def _get_go_repo_lock(manifest_path) -> threading.Lock: return _go_repo_locks[key] -ROOT_LEVEL_SENTINEL = 'root-top-level-agent-morpheus' +ROOT_LEVEL_SENTINEL = 'root-top-level-exploit-iq' TRANSITIVE_ENV_NAME = 'transitive_env' INSTALLED_PACKAGES_FILE = 'installed_packages.txt' diff --git a/src/exploit_iq_commons/utils/document_embedding.py b/src/exploit_iq_commons/utils/document_embedding.py index ad805cd1f..aee69a23a 100644 --- a/src/exploit_iq_commons/utils/document_embedding.py +++ b/src/exploit_iq_commons/utils/document_embedding.py @@ -692,7 +692,7 @@ def build_vdbs(self, # Determine the output path by combining the vdb_directory with the hash of the source documents vdb_output_dir = self.vdb_directory / source_type / str(self.hash_source_documents_info(source_infos)) - if (not vdb_output_dir.exists() or os.environ.get("MORPHEUS_ALWAYS_REBUILD_VDB", "0") == "1"): + if (not vdb_output_dir.exists() or os.environ.get("EXPLOIT_IQ_ALWAYS_REBUILD_VDB", "0") == "1"): vdb = self.create_vdb(source_infos=source_infos, output_path=vdb_output_dir) else: logger.info("Cache hit on VDB. Loading existing FAISS database: %s", vdb_output_dir) diff --git a/src/exploit_iq_commons/utils/functions_parsers/java_functions_parsers.py b/src/exploit_iq_commons/utils/functions_parsers/java_functions_parsers.py index 0bd51f3be..a79b89223 100644 --- a/src/exploit_iq_commons/utils/functions_parsers/java_functions_parsers.py +++ b/src/exploit_iq_commons/utils/functions_parsers/java_functions_parsers.py @@ -28,7 +28,7 @@ strip_java_generics, JAVA_ANNOTATION_SYMBOL, extract_fqcn from exploit_iq_commons.logging.loggers_factory import LoggingFactory -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") PARAMETER = "parameter" diff --git a/src/exploit_iq_commons/utils/java_chain_of_calls_retriever.py b/src/exploit_iq_commons/utils/java_chain_of_calls_retriever.py index 1b2a8e6d5..4dcbc25ee 100644 --- a/src/exploit_iq_commons/utils/java_chain_of_calls_retriever.py +++ b/src/exploit_iq_commons/utils/java_chain_of_calls_retriever.py @@ -36,7 +36,7 @@ create_inheritance_map, get_target_class_names, dummy_package_name from exploit_iq_commons.data_models.input import SourceDocumentsInfo -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") # Lowercase package segments; class segments start with uppercase; allow dots or $ for inners _FQCN_STRICT_RE = re.compile( diff --git a/src/exploit_iq_commons/utils/source_rpm_downloader.py b/src/exploit_iq_commons/utils/source_rpm_downloader.py index 01fa0a240..cf8a85c9b 100644 --- a/src/exploit_iq_commons/utils/source_rpm_downloader.py +++ b/src/exploit_iq_commons/utils/source_rpm_downloader.py @@ -113,7 +113,7 @@ def __init__(self): RepoUrl(url="https://mirror.stream.centos.org/12-stream/AppStream/source/tree/", platform="el12") ] # Default RPM cache directory - self.rpm_cache_dir = "/morpheus-data/rpms/" + self.rpm_cache_dir = "/exploit-iq-data/rpms/" # Unit Test mode toggle (default disabled) self._unit_test_mode: bool = False self._initialized = True @@ -138,7 +138,7 @@ def set_rpm_cache_dir(self, cache_dir: str): except Exception as e: logger.error("Failed to create RPM cache directory %s: %s", cache_dir, e) # Fall back to default directory if creation fails - self.rpm_cache_dir = "/morpheus-data/rpms/" + self.rpm_cache_dir = "/exploit-iq-data/rpms/" logger.warning("Falling back to default cache directory: %s", self.rpm_cache_dir) else: logger.info("RPM cache directory updated to: %s", cache_dir) diff --git a/src/exploit_iq_commons/utils/transitive_code_searcher_tool.py b/src/exploit_iq_commons/utils/transitive_code_searcher_tool.py index 12c3455a3..5c259737c 100644 --- a/src/exploit_iq_commons/utils/transitive_code_searcher_tool.py +++ b/src/exploit_iq_commons/utils/transitive_code_searcher_tool.py @@ -25,7 +25,7 @@ from exploit_iq_commons.logging.loggers_factory import LoggingFactory, MULTI_LINE_MESSAGE_TRUE -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") class TransitiveCodeSearcher: diff --git a/src/vuln_analysis/configs/openapi/openapi.json b/src/vuln_analysis/configs/openapi/openapi.json index e0cdb1ad2..87ee11fd7 100644 --- a/src/vuln_analysis/configs/openapi/openapi.json +++ b/src/vuln_analysis/configs/openapi/openapi.json @@ -14,7 +14,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusInput-Input" + "$ref": "#/components/schemas/ExploitIqInput-Input" } } }, @@ -26,7 +26,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusOutput" + "$ref": "#/components/schemas/ExploitIqOutput" } } } @@ -63,7 +63,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusInput-Input" + "$ref": "#/components/schemas/ExploitIqInput-Input" } } }, @@ -75,7 +75,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusOutput" + "$ref": "#/components/schemas/ExploitIqOutput" } } } @@ -131,7 +131,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusInput-Input" + "$ref": "#/components/schemas/ExploitIqInput-Input" } } } @@ -142,7 +142,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/AgentMorpheusOutput" + "$ref": "#/components/schemas/ExploitIqOutput" } } } @@ -908,162 +908,53 @@ "title": "AcquiredArtifacts", "description": "Resolved file locations populated by source_acquisition, consumed by downstream checker nodes." }, - "AgentIntermediateStep": { + "Affect": { "properties": { - "tool_name": { - "type": "string", - "title": "Tool Name" - }, - "action_log": { - "type": "string", - "title": "Action Log" - }, - "tool_input": { + "ps_module": { "anyOf": [ { "type": "string" }, - { - "type": "object" - } - ], - "title": "Tool Input" - }, - "tool_output": { - "title": "Tool Output" - } - }, - "type": "object", - "required": [ - "tool_name", - "action_log", - "tool_input", - "tool_output" - ], - "title": "AgentIntermediateStep", - "description": "Represents info for an intermediate step taken by an agent." - }, - "AgentMorpheusEngineOutput": { - "properties": { - "vuln_id": { - "type": "string", - "title": "Vuln Id" - }, - "checklist": { - "items": { - "$ref": "#/components/schemas/ChecklistItemOutput" - }, - "type": "array", - "title": "Checklist" - }, - "summary": { - "type": "string", - "title": "Summary" - }, - "justification": { - "$ref": "#/components/schemas/JustificationOutput" - }, - "intel_score": { - "type": "integer", - "title": "Intel Score" - }, - "cvss": { - "anyOf": [ - { - "$ref": "#/components/schemas/CVSSOutput" - }, - { - "type": "null" - } - ] - } - }, - "type": "object", - "required": [ - "vuln_id", - "checklist", - "summary", - "justification", - "intel_score", - "cvss" - ], - "title": "AgentMorpheusEngineOutput", - "description": "Contains all output generated by the main Agent Morpheus LLM Engine for a given vulnerability.\n\n- vuln_id: the ID of the vulnerability being processed by the LLM engine.\n- checklist: a list of ChecklistItemOutput objects, each containing an input and a response from the LLM agent.\n- summary: a short summary of the checklist inputs and responses, generated by an LLM.\n- justification: a JustificationOutput object containing details of the model's justification decision.\n- cvss: a CVSSOutput object containing the CVSS score and vector string for the vulnerability." - }, - "AgentMorpheusInfo": { - "properties": { - "vdb": { - "anyOf": [ - { - "$ref": "#/components/schemas/VdbPaths" - }, - { - "type": "null" - } - ] - }, - "intel": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/CveIntel" - }, - "type": "array" - }, { "type": "null" } ], - "title": "Intel" + "title": "Ps Module" }, - "sbom": { + "ps_product": { "anyOf": [ { - "$ref": "#/components/schemas/SBOMInfo" + "type": "string" }, { "type": "null" } - ] + ], + "title": "Ps Product" }, - "vulnerable_dependencies": { + "ps_component": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/VulnerableDependencies" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Vulnerable Dependencies" + "title": "Ps Component" }, - "checker_context": { + "ps_update_stream": { "anyOf": [ { - "$ref": "#/components/schemas/PackageCheckerContext" + "type": "string" }, { "type": "null" } - ] - } - }, - "type": "object", - "title": "AgentMorpheusInfo", - "description": "Information used for decisioning in the Agent Morpheus engine. These information can all be automatically\ngenerated or retrieved by the pipeline from the input information.\n\n- vdb: paths to source code and documentation vector databases (VDBs) used to understand whether a vulnerability\n is exploitable in the source code.\n- intel: list of CveIntel objects representing intelligence for each vulnerability pulled from various vulnerability\n databases and APIs.\n- sbom: software bill of materials listing the packages and versions in the container image, used to understand\n whether the vulnerable package exists in the image.\n- vulnerable_dependencies: a list of VulnerableDependencies objects for each vuln_id, representing the SBOM packages\n and transitive dependencies that are vulnerable for the vuln_id." - }, - "AgentMorpheusInput-Input": { - "properties": { - "scan": { - "$ref": "#/components/schemas/ScanInfoInput" - }, - "image": { - "$ref": "#/components/schemas/ImageInfoInput-Input" + ], + "title": "Ps Update Stream" }, - "credential_id": { + "resolution": { "anyOf": [ { "type": "string" @@ -1072,20 +963,20 @@ "type": "null" } ], - "title": "Credential Id" + "title": "Resolution" }, - "code_index_success": { + "affectedness": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "title": "Code Index Success" + "title": "Affectedness" }, - "failure_reason": { + "purl": { "anyOf": [ { "type": "string" @@ -1094,89 +985,47 @@ "type": "null" } ], - "title": "Failure Reason", - "default": "No failure reason provided" + "title": "Purl" } }, + "additionalProperties": true, "type": "object", - "required": [ - "scan", - "image" - ], - "title": "AgentMorpheusInput", - "description": "Inputs required by the Agent Morpheus pipeline." + "title": "Affect" }, - "AgentMorpheusInput-Output": { + "AgentIntermediateStep": { "properties": { - "scan": { - "$ref": "#/components/schemas/ScanInfoInput" - }, - "image": { - "$ref": "#/components/schemas/ImageInfoInput-Output" - }, - "credential_id": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Credential Id" + "tool_name": { + "type": "string", + "title": "Tool Name" }, - "code_index_success": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "null" - } - ], - "title": "Code Index Success" + "action_log": { + "type": "string", + "title": "Action Log" }, - "failure_reason": { + "tool_input": { "anyOf": [ { "type": "string" }, { - "type": "null" + "type": "object" } ], - "title": "Failure Reason", - "default": "No failure reason provided" - } - }, - "type": "object", - "required": [ - "scan", - "image" - ], - "title": "AgentMorpheusInput", - "description": "Inputs required by the Agent Morpheus pipeline." - }, - "AgentMorpheusOutput": { - "properties": { - "input": { - "$ref": "#/components/schemas/AgentMorpheusInput-Output" - }, - "info": { - "$ref": "#/components/schemas/AgentMorpheusInfo" + "title": "Tool Input" }, - "output": { - "$ref": "#/components/schemas/OutputPayload" + "tool_output": { + "title": "Tool Output" } }, "type": "object", "required": [ - "input", - "info", - "output" + "tool_name", + "action_log", + "tool_input", + "tool_output" ], - "title": "AgentMorpheusOutput", - "description": "\"\nThe final output of the Agent Morpheus pipeline.\nContains all fields in the AgentMorpheusEngineInput, plus the AgentMorpheusEngineOuput for each input vulnerability." + "title": "AgentIntermediateStep", + "description": "Represents info for an intermediate step taken by an agent." }, "AnalysisType": { "type": "string", @@ -2051,6 +1900,30 @@ "title": "ChatResponseChunk", "description": "ChatResponseChunk is a data model that represents a response chunk from the NAT chat streaming API.\nFully compatible with OpenAI Chat Completions API specification." }, + "CheckedNotVulnerablePackage": { + "properties": { + "name": { + "type": "string", + "title": "Name" + }, + "version": { + "type": "string", + "title": "Version" + }, + "reason": { + "type": "string", + "title": "Reason" + } + }, + "type": "object", + "required": [ + "name", + "version", + "reason" + ], + "title": "CheckedNotVulnerablePackage", + "description": "Information about a package that was checked against CVE intel but determined not vulnerable.\n\n- name: package name that was checked\n- version: installed version of the package\n- reason: LLM-generated explanation of why the package is not vulnerable" + }, "ChecklistItemOutput": { "properties": { "input": { @@ -2328,6 +2201,16 @@ } ] }, + "osidb": { + "anyOf": [ + { + "$ref": "#/components/schemas/CveIntelOsidb" + }, + { + "type": "null" + } + ] + }, "plugin_data": { "items": { "$ref": "#/components/schemas/IntelPluginData" @@ -2694,26 +2577,20 @@ "title": "CveIntelNvd", "description": "Information about an NVD (National Vulnerability Database) entry." }, - "CveIntelRhsa": { + "CveIntelOsidb": { "properties": { - "bugzilla": { - "$ref": "#/components/schemas/Bugzilla" - }, - "details": { + "cve_id": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Details" + "title": "Cve Id" }, - "statement": { + "impact": { "anyOf": [ { "type": "string" @@ -2722,23 +2599,20 @@ "type": "null" } ], - "title": "Statement" + "title": "Impact" }, - "package_state": { + "title": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/PackageState" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Package State" + "title": "Title" }, - "upstream_fix": { + "comment_zero": { "anyOf": [ { "type": "string" @@ -2747,17 +2621,165 @@ "type": "null" } ], - "title": "Upstream Fix" + "title": "Comment Zero" }, - "cvss3": { + "cve_description": { "anyOf": [ { - "$ref": "#/components/schemas/CVSS3" + "type": "string" }, { "type": "null" } - ] + ], + "title": "Cve Description" + }, + "statement": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Statement" + }, + "cwe_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cwe Id" + }, + "source": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Source" + }, + "mitigation": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Mitigation" + }, + "upstream_purls": { + "items": { + "$ref": "#/components/schemas/UpstreamPurl" + }, + "type": "array", + "title": "Upstream Purls", + "default": [] + }, + "affects": { + "items": { + "$ref": "#/components/schemas/Affect" + }, + "type": "array", + "title": "Affects", + "default": [] + }, + "cvss_scores": { + "items": { + "$ref": "#/components/schemas/CvssScore" + }, + "type": "array", + "title": "Cvss Scores", + "default": [] + }, + "references": { + "items": { + "type": "string" + }, + "type": "array", + "title": "References", + "default": [] + } + }, + "additionalProperties": true, + "type": "object", + "title": "CveIntelOsidb", + "description": "Information about an OSIDB (Open Source Information Database) flaw entry.\nAvailable only for internal Red Hat users on VPN." + }, + "CveIntelRhsa": { + "properties": { + "bugzilla": { + "$ref": "#/components/schemas/Bugzilla" + }, + "details": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Details" + }, + "statement": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Statement" + }, + "package_state": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/PackageState" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Package State" + }, + "upstream_fix": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Upstream Fix" + }, + "cvss3": { + "anyOf": [ + { + "$ref": "#/components/schemas/CVSS3" + }, + { + "type": "null" + } + ] } }, "additionalProperties": true, @@ -2862,6 +2884,57 @@ "title": "CveIntelUbuntu", "description": "Information about a Ubuntu CVE entry." }, + "CvssScore": { + "properties": { + "issuer": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Issuer" + }, + "score": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ], + "title": "Score" + }, + "vector": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Vector" + }, + "cvss_version": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Cvss Version" + } + }, + "additionalProperties": true, + "type": "object", + "title": "CvssScore" + }, "DependencyPackage": { "properties": { "system": { @@ -3079,6 +3152,252 @@ "title": "EvaluateStatusResponse", "description": "Response model for the evaluate status endpoint." }, + "ExploitIqEngineOutput": { + "properties": { + "vuln_id": { + "type": "string", + "title": "Vuln Id" + }, + "checklist": { + "items": { + "$ref": "#/components/schemas/ChecklistItemOutput" + }, + "type": "array", + "title": "Checklist" + }, + "summary": { + "type": "string", + "title": "Summary" + }, + "justification": { + "$ref": "#/components/schemas/JustificationOutput" + }, + "intel_score": { + "type": "integer", + "title": "Intel Score" + }, + "cvss": { + "anyOf": [ + { + "$ref": "#/components/schemas/CVSSOutput" + }, + { + "type": "null" + } + ] + }, + "details": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Details" + } + }, + "type": "object", + "required": [ + "vuln_id", + "checklist", + "summary", + "justification", + "intel_score", + "cvss" + ], + "title": "ExploitIqEngineOutput", + "description": "ExploitIqEngineOutputEngine for a given vulnerability.\n\n- vuln_id: the ID of the vulnerability being processed by the LLM engine.\n- checklist: a list of ChecklistItemOutput objects, each containing an input and a response from the LLM agent.\n- summary: a short summary of the checklist inputs and responses, generated by an LLM.\n- justification: a JustificationOutput object containing details of the model's justification decision.\n- cvss: a CVSSOutput object containing the CVSS score and vector string for the vulnerability." + }, + "ExploitIqInfo": { + "properties": { + "vdb": { + "anyOf": [ + { + "$ref": "#/components/schemas/VdbPaths" + }, + { + "type": "null" + } + ] + }, + "intel": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/CveIntel" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Intel" + }, + "sbom": { + "anyOf": [ + { + "$ref": "#/components/schemas/SBOMInfo" + }, + { + "type": "null" + } + ] + }, + "vulnerable_dependencies": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/VulnerableDependencies" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Vulnerable Dependencies" + }, + "checker_context": { + "anyOf": [ + { + "$ref": "#/components/schemas/PackageCheckerContext" + }, + { + "type": "null" + } + ] + } + }, + "type": "object", + "title": "ExploitIqInfo", + "description": "Information used for decisioning in the ExploitIQ engine. These information can all be automatically\ngenerated or retrieved by the pipeline from the input information.\n\n- vdb: paths to source code and documentation vector databases (VDBs) used to understand whether a vulnerability\n is exploitable in the source code.\n- intel: list of CveIntel objects representing intelligence for each vulnerability pulled from various vulnerability\n databases and APIs.\n- sbom: software bill of materials listing the packages and versions in the container image, used to understand\n whether the vulnerable package exists in the image.\n- vulnerable_dependencies: a list of VulnerableDependencies objects for each vuln_id, representing the SBOM packages\n and transitive dependencies that are vulnerable for the vuln_id." + }, + "ExploitIqInput-Input": { + "properties": { + "scan": { + "$ref": "#/components/schemas/ScanInfoInput" + }, + "image": { + "$ref": "#/components/schemas/ImageInfoInput-Input" + }, + "credential_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Credential Id" + }, + "code_index_success": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Code Index Success" + }, + "failure_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Failure Reason", + "default": "No failure reason provided" + } + }, + "type": "object", + "required": [ + "scan", + "image" + ], + "title": "ExploitIqInput", + "description": "Inputs required by the ExploitIQ pipeline." + }, + "ExploitIqInput-Output": { + "properties": { + "scan": { + "$ref": "#/components/schemas/ScanInfoInput" + }, + "image": { + "$ref": "#/components/schemas/ImageInfoInput-Output" + }, + "credential_id": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Credential Id" + }, + "code_index_success": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Code Index Success" + }, + "failure_reason": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Failure Reason", + "default": "No failure reason provided" + } + }, + "type": "object", + "required": [ + "scan", + "image" + ], + "title": "ExploitIqInput", + "description": "Inputs required by the ExploitIQ pipeline." + }, + "ExploitIqOutput": { + "properties": { + "input": { + "$ref": "#/components/schemas/ExploitIqInput-Output" + }, + "info": { + "$ref": "#/components/schemas/ExploitIqInfo" + }, + "output": { + "$ref": "#/components/schemas/OutputPayload" + } + }, + "type": "object", + "required": [ + "input", + "info", + "output" + ], + "title": "ExploitIqOutput", + "description": "\"\nThe final output of the ExploitIQ pipeline.\nContains all fields in the ExploitIqEngineInput, plus the ExploitIqEngineOutput for each input vulnerability." + }, "FileSBOMInfoInput": { "properties": { "_type": { @@ -3533,7 +3852,7 @@ } ], "title": "Downstream Report", - "description": "Serialized DownstreamSearchReport from L1 investigation" + "description": "DownstreamSearchReport from L1 investigation (as dict)" }, "upstream_report": { "anyOf": [ @@ -3545,7 +3864,19 @@ } ], "title": "Upstream Report", - "description": "Serialized UpstreamSearchReport from L1 investigation" + "description": "UpstreamSearchReport from L1 investigation (as dict)" + }, + "git_search_report": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Git Search Report", + "description": "GitSearchReport from git commit search (as dict)" }, "l1_agent_answer": { "anyOf": [ @@ -3667,6 +3998,14 @@ null], "title": "L2 Override Verdict", "description": "L2 verdict override (if any)" + }, + "evidence_sources": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Evidence Sources", + "description": "Sources used for analysis: 'build_log', 'spec_file', 'build_system_files', 'binary'" } }, "type": "object", @@ -3775,7 +4114,7 @@ "properties": { "analysis": { "items": { - "$ref": "#/components/schemas/AgentMorpheusEngineOutput" + "$ref": "#/components/schemas/ExploitIqEngineOutput" }, "type": "array", "title": "Analysis" @@ -3854,11 +4193,11 @@ }, "type": "object", "title": "PackageCheckerContext", - "description": "Consolidates all checker-specific state on AgentMorpheusInfo." + "description": "Consolidates all checker-specific state on ExploitIqInfo." }, "PackageCheckerStatus": { "type": "integer", - "enum": [0, 1, 2, 3, 4, 5], + "enum": [0, 1, 2, 3, 4, 5, 6], "title": "PackageCheckerStatus", "description": "Per-CVE status codes produced by the PackageIdentify phase." }, @@ -3888,6 +4227,18 @@ "$ref": "#/components/schemas/EnumIdentifyResult", "default": "unknown" }, + "rhsa_fix_state": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Rhsa Fix State", + "description": "Raw RHSA fix_state value when is_target_package_affected is based on RHSA assessment" + }, "conclusion_reason": { "type": "string", "title": "Conclusion Reason", @@ -4216,6 +4567,46 @@ "type": "object", "title": "TextContent" }, + "UpstreamPurl": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name" + }, + "purl": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Purl" + }, + "ecosystem": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Ecosystem" + } + }, + "additionalProperties": true, + "type": "object", + "title": "UpstreamPurl" + }, "Usage": { "properties": { "prompt_tokens": { @@ -4510,17 +4901,40 @@ "title": "Search Keywords", "description": "Recommended grep patterns ordered by specificity (most specific first)" }, - "affected_architectures": { + "component_names": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Component Names", + "description": "Module or component names explicitly mentioned in CVE intel (e.g., mod_http2, libxml2_sax). Only extract if explicitly named in advisory/description - do NOT infer or guess." + }, + "affected_bitness": { "type": "string", "enum": [ "32-bit", "64-bit", "both" ], - "title": "Affected Architectures", - "description": "Which CPU architectures are affected: 32-bit only, 64-bit only, or both (default)", + "title": "Affected Bitness", + "description": "Which bitness is affected: 32-bit only, 64-bit only, or both (default)", "default": "both" }, + "affected_architectures": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Affected Architectures", + "description": "CPU families affected (e.g., ['x86', 'arm']). None means all architectures." + }, "is_downstream_patch_available": { "type": "boolean", "title": "Is Downstream Patch Available", @@ -4538,6 +4952,42 @@ "title": "Patch File Name", "description": "Name of the CVE-specific patch file (if available)", "default": "" + }, + "known_mitigations": { + "type": "string", + "title": "Known Mitigations", + "description": "Vendor-provided mitigations from RHSA or other intel sources (e.g., compiler flags, config changes)", + "default": "" + }, + "affected_version_range": { + "type": "string", + "title": "Affected Version Range", + "description": "Affected version range from NVD configurations (e.g., '\u003C 2.4.68')", + "default": "" + }, + "fixed_version": { + "type": "string", + "title": "Fixed Version", + "description": "First fixed version from NVD intel (e.g., '2.4.68')", + "default": "" + }, + "target_version_in_vulnerable_range": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Target Version In Vulnerable Range", + "description": "True if target package version is within the vulnerable range (from Identify phase)" + }, + "target_package_version": { + "type": "string", + "title": "Target Package Version", + "description": "Actual version of the target package being analyzed (e.g., '2.4.63')", + "default": "" } }, "type": "object", @@ -4563,6 +5013,14 @@ }, "type": "array", "title": "Vulnerable Sbom Packages" + }, + "checked_not_vulnerable": { + "items": { + "$ref": "#/components/schemas/CheckedNotVulnerablePackage" + }, + "type": "array", + "title": "Checked Not Vulnerable", + "default": [] } }, "type": "object", @@ -4572,7 +5030,7 @@ "vulnerable_sbom_packages" ], "title": "VulnerableDependencies", - "description": "Information about the vulnerable SBOM packages associated with the vuln_id.\n\n- vuln_id: vulnerability ID (e.g. CVE ID, GHSA ID) associated with the vulnerable package list.\n- vuln_package_intel_sources: list of sources (e.g. \"ghsa\", \"nvd\", \"ubuntu\", \"rhsa\") that provided\n the vulnerable package/version intel for the vuln_id.\n- vulnerable_sbom_packages: list of VulnerableSBOMPackage objects, representing the SBOM packages that are\n vulnerable for a given vuln_id." + "description": "Information about the vulnerable SBOM packages associated with the vuln_id.\n\n- vuln_id: vulnerability ID (e.g. CVE ID, GHSA ID) associated with the vulnerable package list.\n- vuln_package_intel_sources: list of sources (e.g. \"ghsa\", \"nvd\", \"ubuntu\", \"rhsa\") that provided\n the vulnerable package/version intel for the vuln_id.\n- vulnerable_sbom_packages: list of VulnerableSBOMPackage objects, representing the SBOM packages that are\n vulnerable for a given vuln_id.\n- checked_not_vulnerable: list of CheckedNotVulnerablePackage objects, representing packages that were\n checked but determined not vulnerable, along with the LLM reasoning." }, "VulnerableSBOMPackage": { "properties": { diff --git a/src/vuln_analysis/data_models/output.py b/src/vuln_analysis/data_models/output.py index b182c9fa2..76f52ce97 100644 --- a/src/vuln_analysis/data_models/output.py +++ b/src/vuln_analysis/data_models/output.py @@ -19,7 +19,7 @@ from pydantic import Field from pydantic import model_validator -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput class FailureReport(BaseModel): @@ -74,9 +74,9 @@ class JustificationOutput(BaseModel): status: typing.Literal["TRUE", "FALSE", "UNKNOWN"] -class AgentMorpheusEngineOutput(BaseModel): +class ExploitIqEngineOutput(BaseModel): """ - Contains all output generated by the main Agent Morpheus LLM Engine for a given vulnerability. + ExploitIqEngineOutputEngine for a given vulnerability. - vuln_id: the ID of the vulnerability being processed by the LLM engine. - checklist: a list of ChecklistItemOutput objects, each containing an input and a response from the LLM agent. @@ -99,14 +99,14 @@ class OutputPayload(BaseModel): - analysis: per-vulnerability analysis results - vex: the vulnerability exploitability exchange document JSON """ - analysis: list[AgentMorpheusEngineOutput] + analysis: list[ExploitIqEngineOutput] vex: dict[str, typing.Any] | None -class AgentMorpheusOutput(AgentMorpheusEngineInput): +class ExploitIqOutput(ExploitIqEngineInput): """" - The final output of the Agent Morpheus pipeline. - Contains all fields in the AgentMorpheusEngineInput, plus the AgentMorpheusEngineOuput for each input vulnerability. + The final output of the ExploitIQ pipeline. + Contains all fields in the ExploitIqEngineInput, plus the ExploitIqEngineOutput for each input vulnerability. """ output: OutputPayload diff --git a/src/vuln_analysis/data_models/state.py b/src/vuln_analysis/data_models/state.py index 9d147cbb6..5264f9a69 100644 --- a/src/vuln_analysis/data_models/state.py +++ b/src/vuln_analysis/data_models/state.py @@ -18,16 +18,16 @@ from pydantic import BaseModel from exploit_iq_commons.data_models.cve_intel import CveIntel -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput -class AgentMorpheusEngineState(BaseModel): +class ExploitIqEngineState(BaseModel): code_vdb_path: str | None = None doc_vdb_path: str | None = None code_index_path: str | None = None cve_intel: list[CveIntel] transitive_code_searcher: typing.Any | None = None - original_input: AgentMorpheusEngineInput | None = None + original_input: ExploitIqEngineInput | None = None checklist_plans: dict[str, list[str]] = {} checklist_results: dict[str, list[dict[str, typing.Any]]] = {} final_summaries: dict[str, str] = {} diff --git a/src/vuln_analysis/functions/cve_agent.py b/src/vuln_analysis/functions/cve_agent.py index ac2a1d00b..85587139e 100644 --- a/src/vuln_analysis/functions/cve_agent.py +++ b/src/vuln_analysis/functions/cve_agent.py @@ -25,7 +25,7 @@ from langchain_core.messages import HumanMessage from pydantic import Field -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.error_handling_decorator import ToolRaisedException from vuln_analysis.utils.intel_utils import build_critical_context, enrich_go_candidates, enrich_vulnerable_functions_from_patch from vuln_analysis.runtime_context import ctx_state @@ -223,7 +223,7 @@ def _postprocess_results(results: list[tuple], replace_exceptions: bool, replace async def cve_agent(config: CVEAgentExecutorToolConfig, builder: Builder): semaphore = asyncio.Semaphore(config.max_concurrency) if config.max_concurrency else None - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: trace_id.set(state.original_input.input.scan.id) state.uber_jar_file_threshold = config.uber_jar_file_threshold ctx_state.set(state) @@ -274,6 +274,6 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description=("Executes provided checklist of tasks mapped to flagged CVEs to investigate the " "exploitability of a software container by the flagged CVEs.")) diff --git a/src/vuln_analysis/functions/cve_build_agent.py b/src/vuln_analysis/functions/cve_build_agent.py index ad52e3709..0c5b80269 100644 --- a/src/vuln_analysis/functions/cve_build_agent.py +++ b/src/vuln_analysis/functions/cve_build_agent.py @@ -39,7 +39,7 @@ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, RemoveMessage from nat.builder.context import Context -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.functions.react_internals import ( CheckerThought, @@ -127,7 +127,7 @@ class L2InvestigationPhase(StrEnum): async def create_graph_build_agent( config: CVEBuildAgentConfig, builder: Builder, - state: AgentMorpheusEngineInput, + state: ExploitIqEngineInput, tracer, ): """Build the L2 Build Agent LangGraph. @@ -740,7 +740,7 @@ async def investigation_phase_node(state: BuildAgentState) -> dict: async def cve_build_agent(config: CVEBuildAgentConfig, builder: Builder): """Level 2 Build Agent entry point.""" - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: """Run L2 build analysis and populate l2_result on checker_context.""" trace_id.set(message.input.scan.id) tracer = Context.get() diff --git a/src/vuln_analysis/functions/cve_calculate_intel_score.py b/src/vuln_analysis/functions/cve_calculate_intel_score.py index 5f562e4d2..f060efaf0 100644 --- a/src/vuln_analysis/functions/cve_calculate_intel_score.py +++ b/src/vuln_analysis/functions/cve_calculate_intel_score.py @@ -31,17 +31,17 @@ class CVECalculateIntelScoreConfig(FunctionBaseConfig, name="cve_calculate_intel Defines a function that calculates the intel quality generating a score for it. """ llm_name: str = Field(description="The LLM model to use with the CVE agent.") - generate_intel_score: bool = Field(default=True, description="Whether to generate a CVE intel score for the agent morpheus analysis or not.") + generate_intel_score: bool = Field(default=True, description="Whether to generate a CVE intel score for the exploit-iq analysis or not.") intel_low_score: int = Field(default=51, description="The intel low score threshold to stop analysis.") insist_analysis: bool = Field(default=False, description="Whether to continue the analysis even when the intel score is below 'intel_low_score' threshold.") @register_function(config_type=CVECalculateIntelScoreConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_calculate_intel_score(config: CVECalculateIntelScoreConfig, builder: Builder): # pylint: disable=unused-argument - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.utils.intel_source_score import IntelScorer - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: if config.generate_intel_score: intel_scorer = IntelScorer(config, builder) @@ -50,5 +50,5 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: return message yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description="Calculates the CVE source score.") diff --git a/src/vuln_analysis/functions/cve_check_vuln_deps.py b/src/vuln_analysis/functions/cve_check_vuln_deps.py index 08dc972af..e7f3ce898 100644 --- a/src/vuln_analysis/functions/cve_check_vuln_deps.py +++ b/src/vuln_analysis/functions/cve_check_vuln_deps.py @@ -44,10 +44,10 @@ async def cve_check_vuln_deps(config: CVEVulnerableDepsChecksConfig, builder: Bu from exploit_iq_commons.data_models.cve_intel import CveIntel from exploit_iq_commons.data_models.dependencies import VulnerableDependencies from exploit_iq_commons.data_models.dependencies import VulnerableSBOMPackage - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.utils.vulnerable_dependency_checker import VulnerableDependencyChecker - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: trace_id.set(message.input.scan.id) @@ -144,5 +144,5 @@ async def _calc_dep(cve_intel: CveIntel): return message yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description=("Cross-references every entry in the SBOM for known vulnerabilities.")) diff --git a/src/vuln_analysis/functions/cve_checker_report.py b/src/vuln_analysis/functions/cve_checker_report.py index 130d34d2c..b8992fce5 100644 --- a/src/vuln_analysis/functions/cve_checker_report.py +++ b/src/vuln_analysis/functions/cve_checker_report.py @@ -18,7 +18,7 @@ This module provides the report generation node for the L1/L2 pipeline. It consumes L1InvestigationResult (and optionally L2BuildResult) from -checker_context and produces the final AgentMorpheusOutput. +checker_context and produces the final ExploitIqOutput. """ import re @@ -35,7 +35,7 @@ from pydantic import Field from exploit_iq_commons.logging.loggers_factory import LoggingFactory, trace_id -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput from exploit_iq_commons.data_models.checker_status import ( EnumIdentifyResult, L1InvestigationResult, @@ -45,8 +45,8 @@ from nat.builder.context import Context from vuln_analysis.data_models.output import ( - AgentMorpheusEngineOutput, - AgentMorpheusOutput, + ExploitIqEngineOutput, + ExploitIqOutput, JustificationOutput, OutputPayload, ) @@ -768,7 +768,7 @@ def _build_details_md(blocks: ReportBlocks) -> str | None: def _build_report_blocks( - message: AgentMorpheusEngineInput, + message: ExploitIqEngineInput, code_agent_report: CodeAgentReport, cve_description: str, downstream_report: DownstreamSearchReport | None, @@ -994,7 +994,7 @@ def _apply_l2_verdict( def _build_analysis( - message: AgentMorpheusEngineInput, + message: ExploitIqEngineInput, code_agent_report: CodeAgentReport, intel_score: int, cve_description: str = "", @@ -1002,7 +1002,7 @@ def _build_analysis( upstream_report: UpstreamSearchReport | None = None, l1_result: L1InvestigationResult | None = None, l2_result: L2BuildResult | None = None, -) -> list[AgentMorpheusEngineOutput]: +) -> list[ExploitIqEngineOutput]: """Build the final analysis output from the code agent report using ReportBlocks. - summary: LLM executive_summary (verdict, reconciliation, technical context) @@ -1045,7 +1045,7 @@ def _build_analysis( details = _build_details_md(blocks) return [ - AgentMorpheusEngineOutput( + ExploitIqEngineOutput( vuln_id=intel.vuln_id, checklist=[], summary=summary, @@ -1075,7 +1075,7 @@ class CVECheckerReportConfig(FunctionBaseConfig, name="cve_checker_report"): async def cve_checker_report(config: CVECheckerReportConfig, builder: Builder): """Report generation function for the L1/L2 checker pipeline.""" - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusOutput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqOutput: """Generate the final checker report from L1 (and optionally L2) results.""" trace_id.set(message.input.scan.id) tracer = Context.get() @@ -1085,12 +1085,12 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusOutput: ctx = message.info.checker_context if ctx is None or ctx.l1_result is None: logger.error("cve_checker_report: no L1 result available") - return AgentMorpheusOutput( + return ExploitIqOutput( input=message.input, info=message.info, output=OutputPayload( analysis=[ - AgentMorpheusEngineOutput( + ExploitIqEngineOutput( vuln_id=intel.vuln_id, checklist=[], summary="Rpm scanning investigation did not produce results.", @@ -1201,7 +1201,7 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusOutput: if descriptions: cve_description = descriptions[0][1] - return AgentMorpheusOutput( + return ExploitIqOutput( input=message.input, info=message.info, output=OutputPayload( diff --git a/src/vuln_analysis/functions/cve_checker_segmentation.py b/src/vuln_analysis/functions/cve_checker_segmentation.py index bae7f31ee..129c9c1b1 100644 --- a/src/vuln_analysis/functions/cve_checker_segmentation.py +++ b/src/vuln_analysis/functions/cve_checker_segmentation.py @@ -107,11 +107,11 @@ class CVECheckerSegmentationConfig(FunctionBaseConfig, name="cve_checker_segment @register_function(config_type=CVECheckerSegmentationConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_checker_segmentation(config: CVECheckerSegmentationConfig, builder: Builder): - from exploit_iq_commons.data_models.info import AgentMorpheusInfo - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.info import ExploitIqInfo + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.utils.full_text_search import FullTextSearch - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: if not message.info.checker_context or not message.info.checker_context.source_key: logger.info("checker_segmentation: no checker_context.source_keys, skipping indexing") return message @@ -144,7 +144,7 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: elapsed = time.time() - start logger.info("checker_segmentation: indexing completed in %.2fs at %s", elapsed, index_path) - message.info.vdb = AgentMorpheusInfo.VdbPaths(code_index_path=str(index_path)) + message.info.vdb = ExploitIqInfo.VdbPaths(code_index_path=str(index_path)) return message yield FunctionInfo.from_fn( diff --git a/src/vuln_analysis/functions/cve_checklist.py b/src/vuln_analysis/functions/cve_checklist.py index ab6f3c985..ead68f119 100644 --- a/src/vuln_analysis/functions/cve_checklist.py +++ b/src/vuln_analysis/functions/cve_checklist.py @@ -48,7 +48,7 @@ class CVEChecklistToolConfig(FunctionBaseConfig, name="cve_checklist"): @register_function(config_type=CVEChecklistToolConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_checklist(config: CVEChecklistToolConfig, builder: Builder): - from vuln_analysis.data_models.state import AgentMorpheusEngineState + from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.checklist_prompt_generator import _parse_list from vuln_analysis.utils.checklist_prompt_generator import generate_checklist @@ -71,7 +71,7 @@ async def generate_checklist_for_cve(cve_intel, ecosystem: str = ""): return cve_intel["vuln_id"], checklist[0] - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: trace_id.set(state.original_input.input.scan.id) ecosystem = state.original_input.input.image.ecosystem.value if state.original_input and state.original_input.input.image.ecosystem else "" intel_df = data_utils.merge_intel_and_plugin_data_convert_to_dataframe(state.cve_intel) @@ -84,5 +84,5 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description=("Generates tailored, context-sensitive task checklist for impact analysis.")) diff --git a/src/vuln_analysis/functions/cve_clone_and_deps.py b/src/vuln_analysis/functions/cve_clone_and_deps.py index a7e2117ab..74af45dd7 100644 --- a/src/vuln_analysis/functions/cve_clone_and_deps.py +++ b/src/vuln_analysis/functions/cve_clone_and_deps.py @@ -64,9 +64,9 @@ class CVECloneAndDepsConfig(FunctionBaseConfig, name="cve_clone_and_deps"): @register_function(config_type=CVECloneAndDepsConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def clone_and_deps(config: CVECloneAndDepsConfig, builder: Builder): - from exploit_iq_commons.data_models.info import AgentMorpheusInfo - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput - from exploit_iq_commons.data_models.input import AgentMorpheusInput + from exploit_iq_commons.data_models.info import ExploitIqInfo + from exploit_iq_commons.data_models.input import ExploitIqEngineInput + from exploit_iq_commons.data_models.input import ExploitIqInput from exploit_iq_commons.data_models.input import ManualSBOMInfoInput from exploit_iq_commons.utils.document_embedding import DocumentEmbedding from exploit_iq_commons.utils.source_rpm_downloader import RPMDependencyManager @@ -80,7 +80,7 @@ async def clone_and_deps(config: CVECloneAndDepsConfig, builder: Builder): pickle_cache_directory=config.base_pickle_dir, ) - async def _arun(message: AgentMorpheusInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqInput) -> ExploitIqEngineInput: """ Clone repositories and install dependencies. @@ -148,11 +148,11 @@ async def _arun(message: AgentMorpheusInput) -> AgentMorpheusEngineInput: if failure_reason: message.failure_reason = failure_reason - info = AgentMorpheusInfo() - return AgentMorpheusEngineInput(input=message, info=info) + info = ExploitIqInfo() + return ExploitIqEngineInput(input=message, info=info) yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusInput, + input_schema=ExploitIqInput, description="Clone repositories and install dependencies (early phase).", ) diff --git a/src/vuln_analysis/functions/cve_fetch_intel.py b/src/vuln_analysis/functions/cve_fetch_intel.py index b21b9238a..746a38837 100644 --- a/src/vuln_analysis/functions/cve_fetch_intel.py +++ b/src/vuln_analysis/functions/cve_fetch_intel.py @@ -46,10 +46,10 @@ class CVEFetchIntelConfig(FunctionBaseConfig, name="cve_fetch_intel"): @register_function(config_type=CVEFetchIntelConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_fetch_intel(config: CVEFetchIntelConfig, builder: Builder): # pylint: disable=unused-argument - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.utils.intel_retriever import IntelRetriever - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: async def _inner(): async with aiohttp.ClientSession() as session: trace_id.set(message.input.scan.id) @@ -71,5 +71,5 @@ async def _inner(): return message yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description=("Fetches details about CVEs from NIST and CVE Details websites.")) diff --git a/src/vuln_analysis/functions/cve_fetch_patches.py b/src/vuln_analysis/functions/cve_fetch_patches.py index a78b5acbe..168e35864 100644 --- a/src/vuln_analysis/functions/cve_fetch_patches.py +++ b/src/vuln_analysis/functions/cve_fetch_patches.py @@ -38,7 +38,7 @@ class CVEFetchPatchesConfig(FunctionBaseConfig, name="cve_fetch_patches"): @register_function(config_type=CVEFetchPatchesConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_fetch_patches(config: CVEFetchPatchesConfig, builder: Builder): - from vuln_analysis.data_models.state import AgentMorpheusEngineState + from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.intel_utils import extract_commit_url_candidates from vuln_analysis.utils.web_patch_fetcher import fetch_patch_for_cve @@ -47,7 +47,7 @@ async def cve_fetch_patches(config: CVEFetchPatchesConfig, builder: Builder): if config.llm_name else None ) - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: trace_id.set(state.original_input.input.scan.id) intel_map = {intel.vuln_id: intel for intel in state.cve_intel} @@ -87,6 +87,6 @@ async def _throttled_fetch(vuln_id, intel): yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description="Fetches vulnerability fix patches from intel references and OSV API.", ) diff --git a/src/vuln_analysis/functions/cve_file_output.py b/src/vuln_analysis/functions/cve_file_output.py index 6e41d198c..04b05d4b2 100644 --- a/src/vuln_analysis/functions/cve_file_output.py +++ b/src/vuln_analysis/functions/cve_file_output.py @@ -46,7 +46,7 @@ async def output_to_file(config: CVEFileOutputConfig, builder: Builder): # pyli import os from pathlib import Path - from vuln_analysis.data_models.output import AgentMorpheusOutput + from vuln_analysis.data_models.output import ExploitIqOutput from vuln_analysis.utils.output_formatter import generate_vulnerability_reports if (os.path.exists(config.file_path)): @@ -59,7 +59,7 @@ async def output_to_file(config: CVEFileOutputConfig, builder: Builder): # pyli # Ensure our directory exists os.makedirs(os.path.realpath(os.path.dirname(config.file_path)), exist_ok=True) - async def _arun(message: AgentMorpheusOutput) -> AgentMorpheusOutput: + async def _arun(message: ExploitIqOutput) -> ExploitIqOutput: file_path = Path(config.file_path) @@ -78,5 +78,5 @@ async def _arun(message: AgentMorpheusOutput) -> AgentMorpheusOutput: return message yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusOutput, + input_schema=ExploitIqOutput, description=("Outputs workflow results to a file.")) diff --git a/src/vuln_analysis/functions/cve_generate_cvss.py b/src/vuln_analysis/functions/cve_generate_cvss.py index 8343d1c1b..eac6a67d3 100644 --- a/src/vuln_analysis/functions/cve_generate_cvss.py +++ b/src/vuln_analysis/functions/cve_generate_cvss.py @@ -33,7 +33,7 @@ from cvss import CVSS3 from langchain.agents.mrkl.output_parser import MRKLOutputParser -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.tools.tool_names import ToolNames from vuln_analysis.utils.prompting import get_cvss_prompt @@ -183,7 +183,7 @@ def handle_parse_error(exception: OutputParserException) -> str: return handle_parse_error async def _create_agent(config: CVEGenerateCvssToolConfig, builder: Builder, - state: AgentMorpheusEngineState) -> AgentExecutor: + state: ExploitIqEngineState) -> AgentExecutor: tools = builder.get_tools(tool_names=config.tool_names, wrapper_type=LLMFrameworkEnum.LANGCHAIN) llm = await builder.get_llm(llm_name=config.llm_name, wrapper_type=LLMFrameworkEnum.LANGCHAIN) is_openai = "openai" in llm.__class__.__module__.lower() @@ -225,7 +225,7 @@ async def _create_agent(config: CVEGenerateCvssToolConfig, builder: Builder, async def _run_for_vuln(agent: AgentExecutor, - state: AgentMorpheusEngineState, + state: ExploitIqEngineState, vuln_id: str, semaphore: asyncio.Semaphore | None) -> list: state_copy = state.model_copy(deep=True) @@ -293,7 +293,7 @@ async def cve_generate_cvss(config: CVEGenerateCvssToolConfig, builder: Builder) semaphore = asyncio.Semaphore(config.max_concurrency) if config.max_concurrency else None - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: if config.skip: logger.info("`config.skip` is set to True. Skipping CVSS generation.") @@ -322,5 +322,5 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description=("Generates the CVSS (Common Vulnerability Scoring System) score and vector string for the vulnerability analysis results.")) diff --git a/src/vuln_analysis/functions/cve_generate_vdbs.py b/src/vuln_analysis/functions/cve_generate_vdbs.py index 354631eea..0a30ff7eb 100644 --- a/src/vuln_analysis/functions/cve_generate_vdbs.py +++ b/src/vuln_analysis/functions/cve_generate_vdbs.py @@ -71,9 +71,9 @@ class CVEGenerateVDBsToolConfig(FunctionBaseConfig, name="cve_generate_vdbs"): @register_function(config_type=CVEGenerateVDBsToolConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def generate_vdb(config: CVEGenerateVDBsToolConfig, builder: Builder): - from exploit_iq_commons.data_models.info import AgentMorpheusInfo - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput - from exploit_iq_commons.data_models.input import AgentMorpheusInput + from exploit_iq_commons.data_models.info import ExploitIqInfo + from exploit_iq_commons.data_models.input import ExploitIqEngineInput + from exploit_iq_commons.data_models.input import ExploitIqInput from exploit_iq_commons.data_models.input import SourceDocumentsInfo from vuln_analysis.functions.cve_agent import CVEAgentExecutorToolConfig from exploit_iq_commons.utils.document_embedding import DocumentEmbedding @@ -168,7 +168,7 @@ def _build_code_index(source_infos: list[SourceDocumentsInfo]) -> Path | None: code_index_path = FullTextSearch.get_index_directory( base_path=config.base_code_index_dir, hash_value=embedder.hash_source_documents_info(source_infos)) - if (not code_index_path.exists() or os.environ.get("MORPHEUS_ALWAYS_REBUILD_VDB", "0") == "1"): + if (not code_index_path.exists() or os.environ.get("EXPLOIT_IQ_ALWAYS_REBUILD_VDB", "0") == "1"): documents_exists = _create_code_index(source_infos, embedder, code_index_path) else: logger.info("Cache hit on code index. Loading existing code index: %s", code_index_path) @@ -177,7 +177,7 @@ def _build_code_index(source_infos: list[SourceDocumentsInfo]) -> Path | None: else: return None - async def _arun(message: AgentMorpheusInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqInput) -> ExploitIqEngineInput: """ Builds source code and documentation FAISS databases based upon the source repositories. For now we are only storing a path to the FAISS databases in the message. @@ -186,7 +186,7 @@ async def _arun(message: AgentMorpheusInput) -> AgentMorpheusEngineInput: Parameters ---------- - message : AgentMorpheusInput + message : ExploitIQInput The input message build_vdb_fn : typing.Callable[[str, str, list[SourceCodeRepo]], dict[str, str]] The function that builds the VDB database for a given vulnerability scan id, base image, and source code @@ -279,12 +279,12 @@ async def _arun(message: AgentMorpheusInput) -> AgentMorpheusEngineInput: if not code_index_success: message.failure_reason = failure_reason - info = AgentMorpheusInfo(vdb=AgentMorpheusInfo.VdbPaths( + info = ExploitIqInfo(vdb=ExploitIqInfo.VdbPaths( code_vdb_path=vdb_code_path, doc_vdb_path=vdb_doc_path, code_index_path=code_index_path)) message.code_index_success = code_index_success - return AgentMorpheusEngineInput(input=message, info=info) + return ExploitIqEngineInput(input=message, info=info) yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusInput, + input_schema=ExploitIqInput, description=("Generates vector database from code repositories and documentation.")) diff --git a/src/vuln_analysis/functions/cve_generate_vex.py b/src/vuln_analysis/functions/cve_generate_vex.py index a7d2ee4b9..7333554b5 100644 --- a/src/vuln_analysis/functions/cve_generate_vex.py +++ b/src/vuln_analysis/functions/cve_generate_vex.py @@ -20,7 +20,7 @@ from aiq.cli.register_workflow import register_function from aiq.data_models.function import FunctionBaseConfig from pydantic import Field -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.vex.vex_generator_loader import load_vex_generator from exploit_iq_commons.logging.loggers_factory import LoggingFactory @@ -37,7 +37,7 @@ class CVEGenerateVexConfig(FunctionBaseConfig, name="cve_generate_vex"): @register_function(config_type=CVEGenerateVexConfig) async def cve_generate_vex(config: CVEGenerateVexConfig, builder: Builder): - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: if config.skip: logger.info("`config.skip` is set to True. Skipping VEX generation.") return state @@ -61,5 +61,5 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: return state yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description="Generates a custom VEX document for vulnerable components.") diff --git a/src/vuln_analysis/functions/cve_http_output.py b/src/vuln_analysis/functions/cve_http_output.py index 792b0f107..3f9c30e9e 100644 --- a/src/vuln_analysis/functions/cve_http_output.py +++ b/src/vuln_analysis/functions/cve_http_output.py @@ -36,7 +36,7 @@ import re if TYPE_CHECKING: - from vuln_analysis.data_models.output import AgentMorpheusOutput, FailureReport + from vuln_analysis.data_models.output import ExploitIqOutput, FailureReport logger = LoggingFactory.get_agent_logger(__name__) @@ -109,7 +109,7 @@ class OutputPayload: def _build_output_payload( - message: "AgentMorpheusOutput", + message: "ExploitIqOutput", config: CVEHttpOutputConfig, default_json: str, ) -> OutputPayload: @@ -159,10 +159,10 @@ def _build_output_payload( @register_function(config_type=CVEHttpOutputConfig) async def output_to_http(config: CVEHttpOutputConfig, builder: Builder): # pylint: disable=unused-argument - from vuln_analysis.data_models.output import AgentMorpheusOutput + from vuln_analysis.data_models.output import ExploitIqOutput from vuln_analysis.utils import http_utils - async def _arun(message: AgentMorpheusOutput) -> AgentMorpheusOutput: + async def _arun(message: ExploitIqOutput) -> ExploitIqOutput: trace_id.set(message.input.scan.id) @@ -219,7 +219,7 @@ async def _arun(message: AgentMorpheusOutput) -> AgentMorpheusOutput: return message - def _extract_job_data(message: AgentMorpheusOutput) -> Job: + def _extract_job_data(message: ExploitIqOutput) -> Job: agent_config = builder.get_workflow_config() job_id = message.input.scan.id start_time = datetime.fromisoformat(message.input.scan.started_at.replace('Z', '+00:00')) @@ -246,7 +246,7 @@ def _extract_job_data(message: AgentMorpheusOutput) -> Job: env_vars=env_vars, job_output=message_output) yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusOutput, + input_schema=ExploitIqOutput, description=("Sends CVE workflow output to HTTP endpoint.")) diff --git a/src/vuln_analysis/functions/cve_justify.py b/src/vuln_analysis/functions/cve_justify.py index 8124b4222..790b8b805 100644 --- a/src/vuln_analysis/functions/cve_justify.py +++ b/src/vuln_analysis/functions/cve_justify.py @@ -39,7 +39,7 @@ async def cve_justify(config: CVEJustifyToolConfig, builder: Builder): from langchain_core.prompts import PromptTemplate - from vuln_analysis.data_models.state import AgentMorpheusEngineState + from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.justification_parser import JustificationParser jp = JustificationParser() @@ -53,7 +53,7 @@ async def justify_cve(summary): justification_text = await chain.ainvoke({"summary": summary}) return justification_text.content - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: trace_id.set(state.original_input.input.scan.id) results = await asyncio.gather(*(justify_cve(summary) for summary in state.final_summaries.values())) parsed_justification = await asyncio.gather(jp._parse_justification(results)) @@ -69,5 +69,5 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: return state yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description=("Assigns justification label and reason to each CVE based on summary.")) diff --git a/src/vuln_analysis/functions/cve_package_code_agent.py b/src/vuln_analysis/functions/cve_package_code_agent.py index 035396c69..a624bc569 100644 --- a/src/vuln_analysis/functions/cve_package_code_agent.py +++ b/src/vuln_analysis/functions/cve_package_code_agent.py @@ -32,7 +32,7 @@ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, RemoveMessage from nat.builder.context import Context -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.functions.code_agent_graph_defs import ( CodeAgentState, DownstreamSearchReport, @@ -408,7 +408,7 @@ class CVEPackageCodeAgentConfig(FunctionBaseConfig, name="cve_package_code_agent ) -async def create_graph_code_agent(config: CVEPackageCodeAgentConfig, builder: Builder, state: AgentMorpheusEngineInput, tracer): +async def create_graph_code_agent(config: CVEPackageCodeAgentConfig, builder: Builder, state: ExploitIqEngineInput, tracer): # Node name constants THOUGHT_NODE = "think_node" TOOL_NODE = "tool" @@ -1536,7 +1536,7 @@ def should_continue_after_intel(state: CodeAgentState) -> str: @register_function(config_type=CVEPackageCodeAgentConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_package_code_agent(config: CVEPackageCodeAgentConfig, builder: Builder): - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: """Run L1 investigation and return intermediate result for routing to L2 or report generation.""" trace_id.set(message.input.scan.id) tracer = Context.get() diff --git a/src/vuln_analysis/functions/cve_process_sbom.py b/src/vuln_analysis/functions/cve_process_sbom.py index 39a11788b..93a5de421 100644 --- a/src/vuln_analysis/functions/cve_process_sbom.py +++ b/src/vuln_analysis/functions/cve_process_sbom.py @@ -70,8 +70,8 @@ class CVEProcessSBOMConfig(FunctionBaseConfig, name="cve_process_sbom"): @register_function(config_type=CVEProcessSBOMConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_process_sbom(config: CVEProcessSBOMConfig, builder: Builder): # pylint: disable=unused-argument - from exploit_iq_commons.data_models.info import AgentMorpheusInfo - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.info import ExploitIqInfo + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from exploit_iq_commons.data_models.input import FileSBOMInfoInput from exploit_iq_commons.data_models.input import HTTPSBOMInfoInput from exploit_iq_commons.data_models.input import ManualSBOMInfoInput @@ -79,7 +79,7 @@ async def cve_process_sbom(config: CVEProcessSBOMConfig, builder: Builder): # p from vuln_analysis.utils import http_utils from vuln_analysis.utils.http_utils import HTTPMethod - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: def _parse_sbom_packages(sbom_lines: list[str]) -> list[SBOMPackage]: # Extract the packages @@ -112,7 +112,7 @@ def _parse_sbom_packages(sbom_lines: list[str]) -> list[SBOMPackage]: assert isinstance(message.input.image.sbom_info, ManualSBOMInfoInput) # Create the SBOM object - message.info.sbom = AgentMorpheusInfo.SBOMInfo(packages=message.input.image.sbom_info.packages) + message.info.sbom = ExploitIqInfo.SBOMInfo(packages=message.input.image.sbom_info.packages) elif (message.input.image.sbom_info.type == FileSBOMInfoInput.static_type()): assert isinstance(message.input.image.sbom_info, FileSBOMInfoInput) @@ -122,7 +122,7 @@ def _parse_sbom_packages(sbom_lines: list[str]) -> list[SBOMPackage]: sbom_lines = f.readlines() # Create the SBOM object - message.info.sbom = AgentMorpheusInfo.SBOMInfo(packages=_parse_sbom_packages(sbom_lines)) + message.info.sbom = ExploitIqInfo.SBOMInfo(packages=_parse_sbom_packages(sbom_lines)) elif (message.input.image.sbom_info.type == HTTPSBOMInfoInput.static_type()): assert isinstance(message.input.image.sbom_info, HTTPSBOMInfoInput) @@ -137,7 +137,7 @@ def _parse_sbom_packages(sbom_lines: list[str]) -> list[SBOMPackage]: ) except Exception as e: logger.error("Error fetching SBOM from %s: %s", message.input.image.sbom_info.url, e) - message.info.sbom = AgentMorpheusInfo.SBOMInfo(packages=[]) + message.info.sbom = ExploitIqInfo.SBOMInfo(packages=[]) return message try: @@ -146,14 +146,14 @@ def _parse_sbom_packages(sbom_lines: list[str]) -> list[SBOMPackage]: packages = _parse_sbom_packages(sbom_lines) except Exception as e: logger.error("Error parsing SBOM from %s: %s", message.input.image.sbom_info.url, e) - message.info.sbom = AgentMorpheusInfo.SBOMInfo(packages=[]) + message.info.sbom = ExploitIqInfo.SBOMInfo(packages=[]) return message # Create the SBOM object - message.info.sbom = AgentMorpheusInfo.SBOMInfo(packages=packages) + message.info.sbom = ExploitIqInfo.SBOMInfo(packages=packages) return message yield FunctionInfo.from_fn(_arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description=("Prepares and validates input SBOM.")) diff --git a/src/vuln_analysis/functions/cve_segmentation.py b/src/vuln_analysis/functions/cve_segmentation.py index 7a1135d8b..361bb8f93 100644 --- a/src/vuln_analysis/functions/cve_segmentation.py +++ b/src/vuln_analysis/functions/cve_segmentation.py @@ -89,8 +89,8 @@ class DocumentCollectionError(Exception): @register_function(config_type=CVESegmentationConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def segmentation(config: CVESegmentationConfig, builder: Builder): - from exploit_iq_commons.data_models.info import AgentMorpheusInfo - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.info import ExploitIqInfo + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from exploit_iq_commons.data_models.input import SourceDocumentsInfo from exploit_iq_commons.utils.document_embedding import DocumentEmbedding from exploit_iq_commons.utils.standard_library_cache import StandardLibraryCache @@ -185,7 +185,7 @@ def _build_code_index(source_infos: list[SourceDocumentsInfo]) -> Path | None: hash_value=index_embedder.hash_source_documents_info(code_sources), ) - if not code_index_path.exists() or os.environ.get("MORPHEUS_ALWAYS_REBUILD_VDB", "0") == "1": + if not code_index_path.exists() or os.environ.get("EXPLOIT_IQ_ALWAYS_REBUILD_VDB", "0") == "1": documents_exist = _create_code_index(code_sources, index_embedder, code_index_path) if not documents_exist: return None @@ -194,7 +194,7 @@ def _build_code_index(source_infos: list[SourceDocumentsInfo]) -> Path | None: return code_index_path - async def _arun(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """ Build VDBs and code indexes from already-cloned repositories. @@ -265,7 +265,7 @@ async def _arun(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: message.code_index_success = code_index_success - state.info.vdb = AgentMorpheusInfo.VdbPaths( + state.info.vdb = ExploitIqInfo.VdbPaths( code_vdb_path=vdb_code_path, doc_vdb_path=vdb_doc_path, code_index_path=code_index_path, @@ -275,6 +275,6 @@ async def _arun(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description="Build VDBs and code indexes from cloned repositories (late phase).", ) diff --git a/src/vuln_analysis/functions/cve_source_acquisition.py b/src/vuln_analysis/functions/cve_source_acquisition.py index eabb2ded4..6406873c8 100644 --- a/src/vuln_analysis/functions/cve_source_acquisition.py +++ b/src/vuln_analysis/functions/cve_source_acquisition.py @@ -115,9 +115,9 @@ class CVESourceAcquisitionConfig(FunctionBaseConfig, name="cve_source_acquisitio @register_function(config_type=CVESourceAcquisitionConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN]) async def cve_source_acquisition(config: CVESourceAcquisitionConfig, builder: Builder): - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.input import ExploitIqEngineInput - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: logger.info("source_acquisition: starting source code acquisition") rpm_manager = RPMDependencyManager.get_instance() @@ -237,6 +237,6 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description="Downloads source containers and locates package sources by purl and ecosystem.", ) diff --git a/src/vuln_analysis/functions/cve_summarize.py b/src/vuln_analysis/functions/cve_summarize.py index 6c0644ac6..3a7a51265 100644 --- a/src/vuln_analysis/functions/cve_summarize.py +++ b/src/vuln_analysis/functions/cve_summarize.py @@ -77,7 +77,7 @@ async def cve_summarize(config: CVESummarizeToolConfig, builder: Builder): from langchain_core.prompts import PromptTemplate - from vuln_analysis.data_models.state import AgentMorpheusEngineState + from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.prompting import SUMMARY_PROMPT llm = await builder.get_llm(llm_name=config.llm_name, wrapper_type=LLMFrameworkEnum.LANGCHAIN) @@ -134,7 +134,7 @@ async def summarize_cve(results, ecosystem: str = ""): final_summary = await chain.ainvoke({"response": response}) return final_summary.content - async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def _arun(state: ExploitIqEngineState) -> ExploitIqEngineState: trace_id.set(state.original_input.input.scan.id) ecosystem = state.original_input.input.image.ecosystem.value if state.original_input and state.original_input.input.image.ecosystem else "" results = await asyncio.gather(*(summarize_cve(results, ecosystem=ecosystem) for results in state.checklist_results.items())) @@ -143,5 +143,5 @@ async def _arun(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineState, + input_schema=ExploitIqEngineState, description=("Generates concise, human-readable summarization paragraph from agent results.")) diff --git a/src/vuln_analysis/functions/cve_verify_vuln_package.py b/src/vuln_analysis/functions/cve_verify_vuln_package.py index 87a4c5dd5..2b33ca41f 100644 --- a/src/vuln_analysis/functions/cve_verify_vuln_package.py +++ b/src/vuln_analysis/functions/cve_verify_vuln_package.py @@ -380,7 +380,7 @@ async def cve_verify_vuln_package(config: CVEVerifyVulnPackageConfig, builder: B from langchain_core.messages import HumanMessage - from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput + from exploit_iq_commons.data_models.input import ExploitIqEngineInput from vuln_analysis.utils.package_matchers import get_package_matcher, PackageMatch from vuln_analysis.utils.version_check import ( StdlibVulnerabilityResult, @@ -767,10 +767,10 @@ async def _process_cve_intel_loop( return vulnerable_dependencies async def _handle_cpp_image_analysis( - message: AgentMorpheusEngineInput, + message: ExploitIqEngineInput, version_checker, package_matcher, - ) -> AgentMorpheusEngineInput: + ) -> ExploitIqEngineInput: """ Handle C/C++ container IMAGE analysis using SBOM matching and LLM version checking. @@ -818,7 +818,7 @@ async def _handle_cpp_image_analysis( len(vulnerable_dependencies)) return message - async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def _arun(message: ExploitIqEngineInput) -> ExploitIqEngineInput: from vuln_analysis.utils.package_matchers import CppPackageMatcher from exploit_iq_commons.utils.source_rpm_downloader import RPMDependencyManager @@ -937,6 +937,6 @@ async def _arun(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: yield FunctionInfo.from_fn( _arun, - input_schema=AgentMorpheusEngineInput, + input_schema=ExploitIqEngineInput, description="Verifies vulnerable package presence in source dependencies before LLM analysis." ) diff --git a/src/vuln_analysis/register.py b/src/vuln_analysis/register.py index eaeef6039..9115cbc00 100644 --- a/src/vuln_analysis/register.py +++ b/src/vuln_analysis/register.py @@ -25,12 +25,12 @@ from exploit_iq_commons.data_models.common import PipelineMode from exploit_iq_commons.data_models.checker_status import PackageCheckerStatus, PACKAGE_CHECKER_STATUS_DESCRIPTIONS -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput -from exploit_iq_commons.data_models.input import AgentMorpheusInput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput +from exploit_iq_commons.data_models.input import ExploitIqInput from exploit_iq_commons.data_models.input import DEFAULT_FAILURE_REASON -from exploit_iq_commons.data_models.info import AgentMorpheusInfo -from vuln_analysis.data_models.output import AgentMorpheusEngineOutput, AgentMorpheusOutput, JustificationOutput, OutputPayload -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from exploit_iq_commons.data_models.info import ExploitIqInfo +from vuln_analysis.data_models.output import ExploitIqEngineOutput, ExploitIqOutput, JustificationOutput, OutputPayload +from vuln_analysis.data_models.state import ExploitIqEngineState # pylint: disable=unused-import from vuln_analysis.functions import cve_agent from vuln_analysis.functions import cve_checklist @@ -176,32 +176,32 @@ async def cve_agent_workflow(config: CVEAgentWorkflowConfig, builder: Builder): # Define langgraph node functions @catch_pipeline_errors_async - async def add_start_time_node(state: AgentMorpheusInput) -> AgentMorpheusInput: + async def add_start_time_node(state: ExploitIqInput) -> ExploitIqInput: """Adds the start time to the input""" state.scan.started_at = datetime.now(timezone.utc).isoformat() return state @catch_pipeline_errors_async - async def fetch_intel_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def fetch_intel_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Fetch intel for CVE input""" return await cve_fetch_intel_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def calculate_intel_score_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def calculate_intel_score_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Calculate score for intel source""" return await cve_calculate_intel_score_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def process_sbom_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def process_sbom_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Process SBOMs for CVE input""" return await cve_process_sbom_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def verify_vuln_package_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def verify_vuln_package_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Verify vulnerable package presence in source dependencies""" if cve_verify_vuln_package_fn: return await cve_verify_vuln_package_fn.ainvoke(state.model_dump()) @@ -210,12 +210,12 @@ async def verify_vuln_package_node(state: AgentMorpheusEngineInput) -> AgentMorp # --- Split pipeline nodes (clone_and_deps + segmentation) --- @catch_pipeline_errors_async - async def clone_and_deps_node(state: AgentMorpheusInput) -> AgentMorpheusEngineInput: + async def clone_and_deps_node(state: ExploitIqInput) -> ExploitIqEngineInput: """Clone repositories and install dependencies (early phase).""" return await cve_clone_and_deps_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def segmentation_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def segmentation_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Build VDBs and code indexes from cloned repos (late phase).""" result = await cve_segmentation_fn.ainvoke(state.model_dump()) result_dict = result.model_dump() @@ -223,7 +223,7 @@ async def segmentation_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEng result_dict["failure_reason"] = result.input.failure_reason return result_dict - def route_after_verify_vuln_package(state: AgentMorpheusEngineInput) -> str: + def route_after_verify_vuln_package(state: ExploitIqEngineInput) -> str: """Route to segmentation if any CVE is vulnerable, else skip to llm_engine.""" vuln_deps = state.info.vulnerable_dependencies if vuln_deps is None: @@ -231,14 +231,14 @@ def route_after_verify_vuln_package(state: AgentMorpheusEngineInput) -> str: any_vulnerable = any(len(v.vulnerable_sbom_packages) > 0 for v in vuln_deps) return "segmentation" if any_vulnerable else "llm_engine" - def route_after_clone_and_deps(state: AgentMorpheusEngineInput | AgentMorpheusInput) -> str: + def route_after_clone_and_deps(state: ExploitIqEngineInput | ExploitIqInput) -> str: """Route to fetch_intel on success, or failure if clone failed. - Note: state may be AgentMorpheusEngineInput (has .input.failure_reason) or - AgentMorpheusInput (has .failure_reason directly) depending on LangGraph's + Note: state may be ExploitIqEngineInput (has .input.failure_reason) or + ExploitIqInput (has .failure_reason directly) depending on LangGraph's state propagation behavior. """ - if isinstance(state, AgentMorpheusEngineInput): + if isinstance(state, ExploitIqEngineInput): failure_reason = state.input.failure_reason else: failure_reason = state.failure_reason @@ -247,57 +247,57 @@ def route_after_clone_and_deps(state: AgentMorpheusEngineInput | AgentMorpheusIn return "failure" return "fetch_intel" - async def failure_node(state: AgentMorpheusEngineInput) -> AgentMorpheusOutput: + async def failure_node(state: ExploitIqEngineInput) -> ExploitIqOutput: """Handles pipeline failure (e.g., clone/install failed).""" - return AgentMorpheusOutput( + return ExploitIqOutput( input=state.input, info=state.info, output=OutputPayload(analysis=[], vex=None), ) - async def checklist_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def checklist_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Generates a checklist based on CVE input""" return await cve_checklist_fn.ainvoke(state.model_dump()) - async def agent_executor_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def agent_executor_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Executes the checklist using an agent with ReAct prompt.""" return await cve_agent_executor_fn.ainvoke(state.model_dump()) - async def summarize_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def summarize_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Summarizes the results of the execution""" return await cve_summary_fn.ainvoke(state.model_dump()) - async def justify_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def justify_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Generates a justification for the final summary""" return await cve_justify_fn.ainvoke(state.model_dump()) - async def generate_vex_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def generate_vex_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Generates VEX for vulnerable components""" return await cve_generate_vex_fn.ainvoke(state.model_dump()) - async def generate_cvss_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def generate_cvss_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Generates CVSS for the results of the execution""" return await cve_generate_cvss_fn.ainvoke(state.model_dump()) - async def fetch_patches_node(state: AgentMorpheusEngineState) -> AgentMorpheusEngineState: + async def fetch_patches_node(state: ExploitIqEngineState) -> ExploitIqEngineState: """Fetches vulnerability fix patches from intel references and OSV.""" return await cve_fetch_patches_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def add_completed_time_node(state: AgentMorpheusOutput) -> AgentMorpheusOutput: + async def add_completed_time_node(state: ExploitIqOutput) -> ExploitIqOutput: """Adds the completed time to the output""" state.input.scan.completed_at = datetime.now(timezone.utc).isoformat() return state @catch_pipeline_errors_async - async def output_results_node(state: AgentMorpheusOutput) -> AgentMorpheusOutput: + async def output_results_node(state: ExploitIqOutput) -> ExploitIqOutput: """Outputs results using configured output function""" return await cve_output_fn.ainvoke(state.model_dump()) if cve_output_fn else state @@ -305,22 +305,22 @@ async def output_results_node(state: AgentMorpheusOutput) -> AgentMorpheusOutput # --- Package checker path nodes --- @catch_pipeline_errors_async - async def checker_init_state_node(state: AgentMorpheusInput) -> AgentMorpheusEngineInput: - """Bridges AgentMorpheusInput -> AgentMorpheusEngineInput with empty info (skips VDB generation).""" - return AgentMorpheusEngineInput(input=state, info=AgentMorpheusInfo()) + async def checker_init_state_node(state: ExploitIqInput) -> ExploitIqEngineInput: + """Bridges ExploitIqInput -> ExploitIqEngineInput with empty info (skips VDB generation).""" + return ExploitIqEngineInput(input=state, info=ExploitIqInfo()) @catch_pipeline_errors_async - async def checker_fetch_intel_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def checker_fetch_intel_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Fetch intel for CVE input (package checker path). Reuses the same fetch_intel function.""" return await cve_fetch_intel_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def checker_calculate_intel_score_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def checker_calculate_intel_score_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Calculate intel score for CVE input (package checker path).""" return await cve_calculate_intel_score_fn.ainvoke(state.model_dump()) @catch_pipeline_errors_async - async def source_acquisition_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def source_acquisition_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Acquires source code for the target package (source containers, git fallback).""" if cve_source_acquisition_fn: state = await cve_source_acquisition_fn.ainvoke(state.model_dump()) @@ -335,7 +335,7 @@ async def source_acquisition_node(state: AgentMorpheusEngineInput) -> AgentMorph return state @catch_pipeline_errors_async - async def checker_segmentation_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def checker_segmentation_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Builds scoped Tantivy code index from extracted checker sources.""" if cve_checker_segmentation_fn: state = await cve_checker_segmentation_fn.ainvoke(state.model_dump()) @@ -344,17 +344,17 @@ async def checker_segmentation_node(state: AgentMorpheusEngineInput) -> AgentMor return state @catch_pipeline_errors_async - async def l1_code_agent_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def l1_code_agent_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Level 1 Package Code Agent: investigates CVEs using extracted source and Tantivy code index. - Returns AgentMorpheusEngineInput with l1_result populated on checker_context. + Returns ExploitIqEngineInput with l1_result populated on checker_context. """ if cve_package_code_agent_fn: return await cve_package_code_agent_fn.ainvoke(state.model_dump()) logger.warning("Package code agent function not configured, passing state through") return state - def route_after_l1(state: AgentMorpheusEngineInput) -> str: + def route_after_l1(state: ExploitIqEngineInput) -> str: """Route to L2 Build Agent if vulnerable or uncertain, else to report generation.""" ctx = state.info.checker_context if ctx and ctx.l1_result: @@ -364,10 +364,10 @@ def route_after_l1(state: AgentMorpheusEngineInput) -> str: return "generate_report" @catch_pipeline_errors_async - async def l2_build_agent_node(state: AgentMorpheusEngineInput) -> AgentMorpheusEngineInput: + async def l2_build_agent_node(state: ExploitIqEngineInput) -> ExploitIqEngineInput: """Level 2 Build Agent: BuildCompilationCheck + HardeningCheck. - Returns AgentMorpheusEngineInput with l2_result populated on checker_context. + Returns ExploitIqEngineInput with l2_result populated on checker_context. """ if cve_build_agent_fn: return await cve_build_agent_fn.ainvoke(state.model_dump()) @@ -375,19 +375,19 @@ async def l2_build_agent_node(state: AgentMorpheusEngineInput) -> AgentMorpheusE return state @catch_pipeline_errors_async - async def generate_report_node(state: AgentMorpheusEngineInput) -> AgentMorpheusOutput: + async def generate_report_node(state: ExploitIqEngineInput) -> ExploitIqOutput: """Generate the final checker report from L1/L2 investigation results.""" if cve_checker_report_fn: return await cve_checker_report_fn.ainvoke(state.model_dump()) logger.warning("Checker report function not configured, producing empty output") - return AgentMorpheusOutput( + return ExploitIqOutput( input=state.input, info=state.info, output=OutputPayload(analysis=[], vex=None), ) @catch_pipeline_errors_async - async def checker_early_exit_node(state: AgentMorpheusEngineInput) -> AgentMorpheusOutput: + async def checker_early_exit_node(state: ExploitIqEngineInput) -> ExploitIqOutput: """Produces a proper output when source_acquisition exits with a non-OK status.""" ctx = state.info.checker_context status = ctx.status if ctx else None @@ -414,7 +414,7 @@ def _get_justification_label(s: PackageCheckerStatus | None) -> str: return "error" analysis = [ - AgentMorpheusEngineOutput( + ExploitIqEngineOutput( vuln_id=v.vuln_id, checklist=[], summary=reason, @@ -431,26 +431,26 @@ def _get_justification_label(s: PackageCheckerStatus | None) -> str: ) for v in state.input.scan.vulns ] - return AgentMorpheusOutput( + return ExploitIqOutput( input=state.input, info=state.info, output=OutputPayload(analysis=analysis, vex=None), ) - def route_after_source_acquisition(state: AgentMorpheusEngineInput): + def route_after_source_acquisition(state: ExploitIqEngineInput): """Route to checker_segmentation (happy path) or early exit on non-OK status.""" ctx = state.info.checker_context if ctx and ctx.status == PackageCheckerStatus.OK: return "checker_segmentation" return "checker_early_exit" - def route_after_add_start_time(state: AgentMorpheusInput): + def route_after_add_start_time(state: ExploitIqInput): """Route to full pipeline or package checker based on pipeline_mode.""" if state.image.pipeline_mode == PipelineMode.PACKAGE_CHECKER: return "checker_init_state" return "clone_and_deps" # build llm engine subgraph - subgraph_builder = StateGraph(AgentMorpheusEngineState) + subgraph_builder = StateGraph(ExploitIqEngineState) subgraph_builder.add_node("checklist", checklist_node) subgraph_builder.add_node("agent_executor", agent_executor_node) subgraph_builder.add_node("summarize", summarize_node) @@ -472,20 +472,20 @@ def route_after_add_start_time(state: AgentMorpheusInput): subgraph = subgraph_builder.compile() @catch_pipeline_errors_async - async def call_llm_engine_subgraph_node(message: AgentMorpheusEngineInput): + async def call_llm_engine_subgraph_node(message: ExploitIqEngineInput): trace_id.set(message.input.scan.id) subgraph_input = preprocess_engine_input(message) subgraph_input = finalize_preprocess_engine_input(message, subgraph_input, builder) if len(subgraph_input.cve_intel) > 0: results = await subgraph.ainvoke(subgraph_input) - subgraph_output = AgentMorpheusEngineState(**results) + subgraph_output = ExploitIqEngineState(**results) else: subgraph_output = subgraph_input output = postprocess_engine_output(message=message, result=subgraph_output) return output # build parent graph - graph_builder = StateGraph(AgentMorpheusOutput, input=AgentMorpheusInput) + graph_builder = StateGraph(ExploitIqOutput, input=ExploitIqInput) graph_builder.add_node("add_start_time", add_start_time_node) graph_builder.add_node("fetch_intel", fetch_intel_node) graph_builder.add_node("calculate_intel_score_node", calculate_intel_score_node) @@ -575,46 +575,46 @@ async def call_llm_engine_subgraph_node(message: AgentMorpheusEngineInput): graph = graph_builder.compile() #graph.get_graph().draw_mermaid_png(output_file_path="checker_flow.png") - def convert_str_to_agent_morpheus_input(input: str) -> AgentMorpheusInput: - logger.debug("Converting JSON string input to AgentMorpheusInput (length: %d)", len(input)) + def convert_str_to_exploit_iq_input(input: str) -> ExploitIqInput: + logger.debug("Converting JSON string input to ExploitIqInput (length: %d)", len(input)) try: - return AgentMorpheusInput.model_validate_json(input) + return ExploitIqInput.model_validate_json(input) except Exception as e: - logger.error("Failed to convert input to AgentMorpheusInput: %s. Your input needs to be a json string.", e) + logger.error("Failed to convert input to ExploitIqInput: %s. Your input needs to be a json string.", e) raise e - def convert_textio_to_agent_morpheus_input(input: TextIOWrapper) -> AgentMorpheusInput: - logger.debug("Converting TextIOWrapper input to AgentMorpheusInput") + def convert_textio_to_exploit_iq_input(input: TextIOWrapper) -> ExploitIqInput: + logger.debug("Converting TextIOWrapper input to ExploitIqInput") try: data = input.read() - return AgentMorpheusInput.model_validate_json(data) + return ExploitIqInput.model_validate_json(data) except Exception as e: logger.error( - "Failed to convert input to AgentMorpheusInput: %s. Your input needs to be a TextIOWrapper object.", e) + "Failed to convert input to ExploitIqInput: %s. Your input needs to be a TextIOWrapper object.", e) raise e - def convert_agent_morpheus_output_to_str(output: AgentMorpheusOutput) -> str: - logger.debug("Converting AgentMorpheusOutput to JSON string") + def convert_exploit_iq_output_to_str(output: ExploitIqOutput) -> str: + logger.debug("Converting ExploitIqOutput to JSON string") try: return output.model_dump_json() except Exception as e: - logger.error("Failed to convert output to str: %s. Your input needs to be an AgentMorpheusOutput object.", + logger.error("Failed to convert output to str: %s. Your input needs to be an ExploitIqOutput object.", e) raise e - async def _response_fn(input_message: AgentMorpheusInput) -> AgentMorpheusOutput: + async def _response_fn(input_message: ExploitIqInput) -> ExploitIqOutput: results = await graph.ainvoke(input_message) - graph_output = AgentMorpheusOutput(**results) + graph_output = ExploitIqOutput(**results) return graph_output try: yield FunctionInfo.from_fn(_response_fn, description=config.description, - input_schema=AgentMorpheusInput, + input_schema=ExploitIqInput, converters=[ - convert_str_to_agent_morpheus_input, - convert_textio_to_agent_morpheus_input, - convert_agent_morpheus_output_to_str + convert_str_to_exploit_iq_input, + convert_textio_to_exploit_iq_input, + convert_exploit_iq_output_to_str ]) except GeneratorExit: logger.info("Workflow exited early!") diff --git a/src/vuln_analysis/runtime_context.py b/src/vuln_analysis/runtime_context.py index adca2360e..088bd8097 100644 --- a/src/vuln_analysis/runtime_context.py +++ b/src/vuln_analysis/runtime_context.py @@ -18,7 +18,7 @@ import contextvars -# Holds the current AgentMorpheusEngineState for the active task +# Holds the current ExploitIqEngineState for the active task ctx_state = contextvars.ContextVar("ctx_state", default="default_value") # Source scope for CU agent tools (Docs Semantic Search, Code Keyword Search). diff --git a/src/vuln_analysis/tools/serp.py b/src/vuln_analysis/tools/serp.py index 7fe820b82..3ca61bf73 100644 --- a/src/vuln_analysis/tools/serp.py +++ b/src/vuln_analysis/tools/serp.py @@ -35,9 +35,9 @@ class SerpWrapperToolConfig(FunctionBaseConfig, name=("%s" % SERP_WRAPPER)): @register_function(config_type=SerpWrapperToolConfig) async def serp_wrapper(config: SerpWrapperToolConfig, builder: Builder): # pylint: disable=unused-argument - from vuln_analysis.utils.serp_api_wrapper import MorpheusSerpAPIWrapper + from vuln_analysis.utils.serp_api_wrapper import ExploitIqSerpAPIWrapper - search = MorpheusSerpAPIWrapper(max_retries=config.max_retries) + search = ExploitIqSerpAPIWrapper(max_retries=config.max_retries) @catch_tool_errors(SERP_WRAPPER) async def _arun(query: str) -> str: diff --git a/src/vuln_analysis/tools/tests/test_concurrency.py b/src/vuln_analysis/tools/tests/test_concurrency.py index 4e62fc9f7..8dcafd494 100644 --- a/src/vuln_analysis/tools/tests/test_concurrency.py +++ b/src/vuln_analysis/tools/tests/test_concurrency.py @@ -16,7 +16,7 @@ from exploit_iq_commons.utils.java_chain_of_calls_retriever import JavaChainOfCallsRetriever from exploit_iq_commons.utils.transitive_code_searcher_tool import TransitiveCodeSearcher -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.tools.transitive_code_search import ( _build_or_get_cached, _build_searcher, @@ -26,7 +26,7 @@ _repo_build_locks, ) -_DEFAULT_THRESHOLD = AgentMorpheusEngineState.model_fields["uber_jar_file_threshold"].default +_DEFAULT_THRESHOLD = ExploitIqEngineState.model_fields["uber_jar_file_threshold"].default # --------------------------------------------------------------------------- @@ -645,7 +645,7 @@ def _make_vuln_dep(self, has_vulns: bool): return mock def _make_engine_input(self, vuln_deps): - """Create a mock AgentMorpheusEngineInput with vulnerable_dependencies.""" + """Create a mock ExploitIqEngineInput with vulnerable_dependencies.""" mock = MagicMock() mock.info.vulnerable_dependencies = vuln_deps return mock diff --git a/src/vuln_analysis/tools/tests/test_transitive_code_search.py b/src/vuln_analysis/tools/tests/test_transitive_code_search.py index 80d3dd8f9..1950d5c00 100644 --- a/src/vuln_analysis/tools/tests/test_transitive_code_search.py +++ b/src/vuln_analysis/tools/tests/test_transitive_code_search.py @@ -5,11 +5,11 @@ from langchain_core.documents import Document from exploit_iq_commons.data_models.common import AnalysisType -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.tools.transitive_code_search import transitive_search, TransitiveCodeSearchToolConfig, _searcher_cache -from exploit_iq_commons.data_models.input import (AgentMorpheusEngineInput, AgentMorpheusInput, +from exploit_iq_commons.data_models.input import (ExploitIqEngineInput, ExploitIqInput, ImageInfoInput, SourceDocumentsInfo, ManualSBOMInfoInput -, SBOMPackage, ScanInfoInput, VulnInfo, AgentMorpheusInfo) +, SBOMPackage, ScanInfoInput, VulnInfo, ExploitIqInfo) from vuln_analysis.runtime_context import ctx_state from vuln_analysis.tools.tests.mock_documents import (python_script_example, python_init_function_example, python_full_document_example, python_parse_function_example, @@ -147,16 +147,16 @@ def set_input_for_next_run(git_repository: str, git_ref: str, included_extension sbom_info_input = ManualSBOMInfoInput(packages=sbom_packages) else: sbom_info_input = ManualSBOMInfoInput(packages=[SBOMPackage(name="a", version="1.0", system="blabla")]) - morpheus_input = AgentMorpheusInput(image=ImageInfoInput(source_info=source_code_info, - sbom_info=sbom_info_input, - analysis_type=AnalysisType.IMAGE), - scan=ScanInfoInput(vulns=[VulnInfo(vuln_id="CVE-2025-1234")])) - engine_input = AgentMorpheusEngineInput(input=morpheus_input, info=AgentMorpheusInfo()) - state: AgentMorpheusEngineState = AgentMorpheusEngineState(original_input=engine_input, - code_vdb_path="", - doc_vdb_path="", - code_index_path="", - cve_intel=[]) + exploit_iq_input = ExploitIqInput(image=ImageInfoInput(source_info=source_code_info, + sbom_info=sbom_info_input, + analysis_type=AnalysisType.IMAGE), + scan=ScanInfoInput(vulns=[VulnInfo(vuln_id="CVE-2025-1234")])) + engine_input = ExploitIqEngineInput(input=exploit_iq_input, info=ExploitIqInfo()) + state: ExploitIqEngineState = ExploitIqEngineState(original_input=engine_input, + code_vdb_path="", + doc_vdb_path="", + code_index_path="", + cve_intel=[]) ctx_state.set(state) @@ -487,6 +487,7 @@ async def test_transitive_search_java_4(): assert len(list_path) > 1 assert 'src/main/java/io/cryostat' in list_path[-1] +@pytest.mark.skip @pytest.mark.asyncio async def test_java_script_transitive_search_1(): """Test that runs with a real repository""" @@ -510,7 +511,7 @@ async def test_java_script_transitive_search_1(): assert path_found == True assert len(list_path) == 2 - +@pytest.mark.skip @pytest.mark.asyncio async def test_java_script_transitive_search_2(): """Test that runs with a real repository""" diff --git a/src/vuln_analysis/tools/transitive_code_search.py b/src/vuln_analysis/tools/transitive_code_search.py index e030dbcfd..b1a911346 100644 --- a/src/vuln_analysis/tools/transitive_code_search.py +++ b/src/vuln_analysis/tools/transitive_code_search.py @@ -26,7 +26,7 @@ from langchain.docstore.document import Document -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from exploit_iq_commons.utils.document_embedding import DocumentEmbedding from exploit_iq_commons.data_models.input import SourceDocumentsInfo from exploit_iq_commons.utils.chain_of_calls_retriever_base import ChainOfCallsRetrieverBase @@ -348,7 +348,7 @@ async def _build_or_get_cached(si, query: str, uber_jar_file_threshold: int, bas async def get_transitive_code_searcher(query: str, base_dirs: tuple): - state: AgentMorpheusEngineState = ctx_state.get() + state: ExploitIqEngineState = ctx_state.get() si = state.original_input.input.image.source_info threshold = state.uber_jar_file_threshold diff --git a/src/vuln_analysis/utils/function_name_extractor.py b/src/vuln_analysis/utils/function_name_extractor.py index b3d19e467..7f955f330 100644 --- a/src/vuln_analysis/utils/function_name_extractor.py +++ b/src/vuln_analysis/utils/function_name_extractor.py @@ -20,7 +20,7 @@ from exploit_iq_commons.logging.loggers_factory import LoggingFactory -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") def traverse_all_parameters(function_ending_index_end, function_prefix_index_end, function_string): diff --git a/src/vuln_analysis/utils/function_name_locator.py b/src/vuln_analysis/utils/function_name_locator.py index 4530ec24a..99f42e40e 100644 --- a/src/vuln_analysis/utils/function_name_locator.py +++ b/src/vuln_analysis/utils/function_name_locator.py @@ -20,12 +20,12 @@ from exploit_iq_commons.utils.chain_of_calls_retriever_base import ChainOfCallsRetrieverBase from exploit_iq_commons.utils.dep_tree import Ecosystem from exploit_iq_commons.utils.standard_library_cache import StandardLibraryCache -from vuln_analysis.utils.serp_api_wrapper import MorpheusSerpAPIWrapper +from vuln_analysis.utils.serp_api_wrapper import ExploitIqSerpAPIWrapper from exploit_iq_commons.utils.source_rpm_downloader import RPMDependencyManager from vuln_analysis.utils.prompt_factory import FL_EXAMPLES -logger = LoggingFactory.get_agent_logger(f"morpheus.{__name__}") +logger = LoggingFactory.get_agent_logger(f"exploit-iq.{__name__}") class FunctionNameLocator: @@ -467,7 +467,7 @@ async def quick_standard_lib_check(package_name: str, ecosystem: Ecosystem) -> t True if package is standard library, False otherwise """ try: - search = MorpheusSerpAPIWrapper(max_retries=2) + search = ExploitIqSerpAPIWrapper(max_retries=2) result = await search.arun(f"Is '{package_name}' part of the {ecosystem.value} standard library?") logger.info("quick_standard_lib_check Standard library check result: %s", result) text = str(result).lower() diff --git a/src/vuln_analysis/utils/llm_engine_utils.py b/src/vuln_analysis/utils/llm_engine_utils.py index 1857bdd89..bc3dd7d12 100644 --- a/src/vuln_analysis/utils/llm_engine_utils.py +++ b/src/vuln_analysis/utils/llm_engine_utils.py @@ -21,10 +21,10 @@ from exploit_iq_commons.data_models.common import AnalysisType from exploit_iq_commons.data_models.dependencies import CheckedNotVulnerablePackage, VulnerableDependencies -from exploit_iq_commons.data_models.input import AgentMorpheusEngineInput -from exploit_iq_commons.data_models.input import AgentMorpheusInput -from vuln_analysis.data_models.output import AgentMorpheusEngineOutput -from vuln_analysis.data_models.output import AgentMorpheusOutput +from exploit_iq_commons.data_models.input import ExploitIqEngineInput +from exploit_iq_commons.data_models.input import ExploitIqInput +from vuln_analysis.data_models.output import ExploitIqEngineOutput +from vuln_analysis.data_models.output import ExploitIqOutput from vuln_analysis.data_models.output import OutputPayload from vuln_analysis.data_models.output import ChecklistItemOutput from vuln_analysis.data_models.output import JustificationOutput @@ -34,7 +34,7 @@ from vuln_analysis.functions.code_agent_graph_defs import PatchFile from vuln_analysis.utils.intel_utils import TEST_FILE_RE from vuln_analysis.functions.cve_checker_report import infer_language_from_path, NON_CODE_LANGUAGES -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from aiq.builder.builder import Builder from vuln_analysis.functions.cve_calculate_intel_score import CVECalculateIntelScoreConfig @@ -44,7 +44,7 @@ logger = LoggingFactory.get_agent_logger(__name__) -def preprocess_engine_input(message: AgentMorpheusEngineInput) -> AgentMorpheusEngineState: +def preprocess_engine_input(message: ExploitIqEngineInput) -> ExploitIqEngineState: assert message.info.intel is not None, "The input message must have intel information" @@ -55,7 +55,7 @@ def preprocess_engine_input(message: AgentMorpheusEngineInput) -> AgentMorpheusE if not sbom: raise ValueError(f"No SBOM packages found for image {image}. Skipping the LLM Engine.") - am_input: AgentMorpheusInput = message.input + am_input: ExploitIqInput = message.input # Scan through the VDC output for CVE's to run through the agent if message.info.vulnerable_dependencies is None: @@ -93,11 +93,11 @@ def preprocess_engine_input(message: AgentMorpheusEngineInput) -> AgentMorpheusE logger.info("Passing %d vuln_id(s) with vulnerable dependencies to the LLM Engine", len(vulns_for_agent)) vdb = message.info.vdb - return AgentMorpheusEngineState(code_vdb_path=vdb.code_vdb_path if vdb else None, - doc_vdb_path=vdb.doc_vdb_path if vdb else None, - code_index_path=vdb.code_index_path if vdb else None, - cve_intel=filtered_intel, - original_input=message) + return ExploitIqEngineState(code_vdb_path=vdb.code_vdb_path if vdb else None, + doc_vdb_path=vdb.doc_vdb_path if vdb else None, + code_index_path=vdb.code_index_path if vdb else None, + cve_intel=filtered_intel, + original_input=message) _MAX_SNIPPET_LINES = 12 @@ -171,15 +171,15 @@ def _build_full_pipeline_details_md(patch_result: WebPatchResult | dict | None) return result or None -def parse_agent_morpheus_engine_output(vuln_id: str, - checklist_results: list[dict[str, typing.Any]], - summary: str, - justification: dict[str, str], - intel_score: int, - cvss: dict[str, str] | None, - patch_result: WebPatchResult | dict | None = None) -> AgentMorpheusEngineOutput: +def parse_exploit_iq_engine_output(vuln_id: str, + checklist_results: list[dict[str, typing.Any]], + summary: str, + justification: dict[str, str], + intel_score: int, + cvss: dict[str, str] | None, + patch_result: WebPatchResult | dict | None = None) -> ExploitIqEngineOutput: """ - Parse the output fields for a single vulnerability into an AgentMorpheusEngineOutput object. + Parse the output fields for a single vulnerability into an ExploitIqEngineOutput object. """ # Convert list of checklist item dicts to list of ChecklistItemOutput objects checklist_output = [ @@ -201,16 +201,16 @@ def parse_agent_morpheus_engine_output(vuln_id: str, details = _build_full_pipeline_details_md(patch_result) - return AgentMorpheusEngineOutput(vuln_id=vuln_id, - checklist=checklist_output, - summary=summary, - justification=justification_output, - intel_score=intel_score, - cvss=cvss_output, - details=details) + return ExploitIqEngineOutput(vuln_id=vuln_id, + checklist=checklist_output, + summary=summary, + justification=justification_output, + intel_score=intel_score, + cvss=cvss_output, + details=details) -def build_deficient_intel_output(vuln_id: str) -> AgentMorpheusEngineOutput: +def build_deficient_intel_output(vuln_id: str) -> ExploitIqEngineOutput: summary = ("There is insufficient intel available to determine vulnerability. " "This is either due to the CVE not existing or there is not enough " "gathered intel for the agent to make an informed decision.") @@ -219,7 +219,7 @@ def build_deficient_intel_output(vuln_id: str) -> AgentMorpheusEngineOutput: status="UNKNOWN") cvss = None - return AgentMorpheusEngineOutput( + return ExploitIqEngineOutput( vuln_id=vuln_id, checklist=[ ChecklistItemOutput(input="Agent bypassed: Insufficient intel gathered. No checklist generated.", @@ -235,7 +235,7 @@ def build_deficient_intel_output(vuln_id: str) -> AgentMorpheusEngineOutput: def build_no_vuln_packages_output( vuln_id: str, checked_not_vulnerable: list[CheckedNotVulnerablePackage] | None = None -) -> AgentMorpheusEngineOutput: +) -> ExploitIqEngineOutput: if checked_not_vulnerable: n = len(checked_not_vulnerable) reasons = {pkg.reason for pkg in checked_not_vulnerable} @@ -273,7 +273,7 @@ def build_no_vuln_packages_output( status="FALSE") cvss = None - return AgentMorpheusEngineOutput( + return ExploitIqEngineOutput( vuln_id=vuln_id, checklist=[ ChecklistItemOutput(input="Agent bypassed: no vulnerable packages detected. Checklist not generated.", @@ -286,7 +286,7 @@ def build_no_vuln_packages_output( cvss=cvss) -def build_no_sbom_output(vuln_id: str) -> AgentMorpheusEngineOutput: +def build_no_sbom_output(vuln_id: str) -> ExploitIqEngineOutput: summary = ("There were no SBOM packages found for the image. This is either due to " "an invalid SBOM input or empty SBOM. There is not enough information " "to make an informed decision.") @@ -295,20 +295,20 @@ def build_no_sbom_output(vuln_id: str) -> AgentMorpheusEngineOutput: status="UNKNOWN") cvss = None - return AgentMorpheusEngineOutput(vuln_id=vuln_id, - checklist=[ + return ExploitIqEngineOutput(vuln_id=vuln_id, + checklist=[ ChecklistItemOutput( input="Agent bypassed: no SBOM packages found. Checklist not generated.", response=summary, intermediate_steps=None) ], - summary=summary, - justification=justification, - intel_score=0, - cvss=cvss) + summary=summary, + justification=justification, + intel_score=0, + cvss=cvss) -def build_low_intel_score_output(vuln_id: str, intel_score: int) -> AgentMorpheusEngineOutput: +def build_low_intel_score_output(vuln_id: str, intel_score: int) -> ExploitIqEngineOutput: summary = ("There is poor quality intel available to determine vulnerability. There is not enough gathered intel" " for the agent to make an informed decision.") justification = JustificationOutput(label="poor_quality_intel", @@ -316,7 +316,7 @@ def build_low_intel_score_output(vuln_id: str, intel_score: int) -> AgentMorpheu status="UNKNOWN") cvss = None - return AgentMorpheusEngineOutput( + return ExploitIqEngineOutput( vuln_id=vuln_id, checklist=[], summary=summary, @@ -325,8 +325,8 @@ def build_low_intel_score_output(vuln_id: str, intel_score: int) -> AgentMorpheu cvss=cvss ) -def postprocess_engine_output(message: AgentMorpheusEngineInput, - result: AgentMorpheusEngineState) -> AgentMorpheusOutput: +def postprocess_engine_output(message: ExploitIqEngineInput, + result: ExploitIqEngineState) -> ExploitIqOutput: trace_id.set(message.input.scan.id) vulnerable_dependencies: list[VulnerableDependencies] | None = message.info.vulnerable_dependencies @@ -351,7 +351,7 @@ def postprocess_engine_output(message: AgentMorpheusEngineInput, if not message.input.image.analysis_type == AnalysisType.SOURCE and not message.info.sbom.packages: output = [build_no_sbom_output(vuln_id) for vuln_id in input_vuln_ids] else: - output: list[AgentMorpheusEngineOutput] = [] + output: list[ExploitIqEngineOutput] = [] output_vuln_ids = list(result.final_summaries.keys()) poor_quality_intel_vul = result.poor_quality_intel_vul @@ -362,13 +362,13 @@ def postprocess_engine_output(message: AgentMorpheusEngineInput, justification = result.justifications[vuln_id] is_vulnerable = justification.get("affected_status") == "TRUE" output.append( - parse_agent_morpheus_engine_output(vuln_id=vuln_id, - checklist_results=result.checklist_results[vuln_id], - summary=result.final_summaries[vuln_id], - justification=justification, - intel_score=intel_map[vuln_id].intel_score, - cvss=result.cvss_results.get(vuln_id, None), - patch_result=result.patch_results.get(vuln_id) if is_vulnerable else None)) + parse_exploit_iq_engine_output(vuln_id=vuln_id, + checklist_results=result.checklist_results[vuln_id], + summary=result.final_summaries[vuln_id], + justification=justification, + intel_score=intel_map[vuln_id].intel_score, + cvss=result.cvss_results.get(vuln_id, None), + patch_result=result.patch_results.get(vuln_id) if is_vulnerable else None)) elif vuln_id in deficient_intel: output.append(build_deficient_intel_output(vuln_id)) elif vuln_id in poor_quality_intel_vul: @@ -387,9 +387,9 @@ def postprocess_engine_output(message: AgentMorpheusEngineInput, out.cvss.score if out.cvss else "-") payload = OutputPayload(analysis=output, vex=result.vex) - return AgentMorpheusOutput(input=message.input, info=message.info, output=payload) + return ExploitIqOutput(input=message.input, info=message.info, output=payload) -def finalize_preprocess_engine_input(message: AgentMorpheusEngineInput, engine_state: AgentMorpheusEngineState, builder: Builder) -> AgentMorpheusEngineState: +def finalize_preprocess_engine_input(message: ExploitIqEngineInput, engine_state: ExploitIqEngineState, builder: Builder) -> ExploitIqEngineState: config = builder.get_function_config("cve_calculate_intel_score") assert isinstance(config, CVECalculateIntelScoreConfig) diff --git a/src/vuln_analysis/utils/output_formatter.py b/src/vuln_analysis/utils/output_formatter.py index c01b9d9be..612d233a4 100644 --- a/src/vuln_analysis/utils/output_formatter.py +++ b/src/vuln_analysis/utils/output_formatter.py @@ -19,17 +19,17 @@ from dateutil.parser import parse -from vuln_analysis.data_models.output import AgentMorpheusOutput +from vuln_analysis.data_models.output import ExploitIqOutput from exploit_iq_commons.utils.data_utils import safe_getattr -def generate_vulnerability_reports(model_dict: AgentMorpheusOutput, output_dir): +def generate_vulnerability_reports(model_dict: ExploitIqOutput, output_dir): """ Creates a markdown file for each CVE ID in the markdown content dictionary. Parameters ---------- - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. output_dir : str The directory where the markdown files will be created. @@ -57,13 +57,13 @@ def generate_vulnerability_reports(model_dict: AgentMorpheusOutput, output_dir): f.write("\n".join(content)) -def _transform_to_markdown(model_dict: AgentMorpheusOutput): +def _transform_to_markdown(model_dict: ExploitIqOutput): """ Convert JSON data to Markdown content. Parameters ---------- - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -86,7 +86,7 @@ def _transform_to_markdown(model_dict: AgentMorpheusOutput): return markdown_content -def _add_header(markdown_content, model_dict: AgentMorpheusOutput): +def _add_header(markdown_content, model_dict: ExploitIqOutput): """ Add header to Markdown content. @@ -94,7 +94,7 @@ def _add_header(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -114,7 +114,7 @@ def _add_header(markdown_content, model_dict: AgentMorpheusOutput): markdown_content[cve_id].append(f"> **Status:** {_get_expoiltability_text(output.justification.status)}") -def _add_cve_intel(markdown_content, model_dict: AgentMorpheusOutput): +def _add_cve_intel(markdown_content, model_dict: ExploitIqOutput): """ Add CVE intelligence details to Markdown content. @@ -122,7 +122,7 @@ def _add_cve_intel(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -263,7 +263,7 @@ def _get_cve_description(intel_obj): return None -def _add_table_of_contents(markdown_content, model_dict: AgentMorpheusOutput): +def _add_table_of_contents(markdown_content, model_dict: ExploitIqOutput): """ Add a table of contents for checklists per CVE. @@ -271,7 +271,7 @@ def _add_table_of_contents(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -297,7 +297,7 @@ def _add_table_of_contents(markdown_content, model_dict: AgentMorpheusOutput): markdown_content[cve_id].append(f"\t {j}. [{intermediate_step}](#checklist-step-{i}.{j})") -def _add_checklist_info(markdown_content, model_dict: AgentMorpheusOutput): +def _add_checklist_info(markdown_content, model_dict: ExploitIqOutput): """ Add detailed information for checklists associated with each CVE. @@ -305,7 +305,7 @@ def _add_checklist_info(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -408,7 +408,7 @@ def _process_tool_output(content): return content_markdown + table -def _add_vulnerability_analysis(markdown_content, model_dict: AgentMorpheusOutput): +def _add_vulnerability_analysis(markdown_content, model_dict: ExploitIqOutput): """ Add vulnerability analysis details to Markdown content. @@ -416,7 +416,7 @@ def _add_vulnerability_analysis(markdown_content, model_dict: AgentMorpheusOutpu ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -438,7 +438,7 @@ def _add_vulnerability_analysis(markdown_content, model_dict: AgentMorpheusOutpu markdown_content[cve_id].append(f"\n{justification.reason}") -def _add_vulnerable_sboms(markdown_content, model_dict: AgentMorpheusOutput): +def _add_vulnerable_sboms(markdown_content, model_dict: ExploitIqOutput): """ Add information about vulnerable SBOM dependencies to Markdown content. @@ -446,7 +446,7 @@ def _add_vulnerable_sboms(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing vulnerability information. Returns @@ -485,7 +485,7 @@ def _add_vulnerable_sboms(markdown_content, model_dict: AgentMorpheusOutput): unique_rows.add(row) # Add the row to the set of unique rows -def _add_references(markdown_content, model_dict: AgentMorpheusOutput): +def _add_references(markdown_content, model_dict: ExploitIqOutput): """ Add references for a CVE from all available sources in the intel object. @@ -493,7 +493,7 @@ def _add_references(markdown_content, model_dict: AgentMorpheusOutput): ---------- markdown_content : dict Markdown content for each CVE ID. - model_dict : AgentMorpheusOutput + model_dict : ExploitIqOutput JSON data containing references information. Returns diff --git a/src/vuln_analysis/utils/serp_api_wrapper.py b/src/vuln_analysis/utils/serp_api_wrapper.py index eecac3514..c8af45506 100644 --- a/src/vuln_analysis/utils/serp_api_wrapper.py +++ b/src/vuln_analysis/utils/serp_api_wrapper.py @@ -25,7 +25,7 @@ from vuln_analysis.utils.url_utils import url_join -class MorpheusSerpAPIWrapper(SerpAPIWrapper): +class ExploitIqSerpAPIWrapper(SerpAPIWrapper): """Custom SerpAPI wrapper with multi-key rotation support. This wrapper extends the standard SerpAPIWrapper to support multiple API keys @@ -59,7 +59,7 @@ def serp_api_key_index(self) -> int: """Shared current key index.""" return self.__class__._serp_api_key_index @model_validator(mode="after") - def validate_base_url(self) -> "MorpheusSerpAPIWrapper": + def validate_base_url(self) -> "ExploitIqSerpAPIWrapper": """Validate the base URL from the environment.""" self.base_url = get_from_env(key="base_url", env_key="SERPAPI_BASE_URL", default=self.base_url) if not self.base_url: @@ -68,7 +68,7 @@ def validate_base_url(self) -> "MorpheusSerpAPIWrapper": self.search_engine.BACKEND = self.base_url return self @model_validator(mode="after") - def validate_serp_api_keys(self) -> "MorpheusSerpAPIWrapper": + def validate_serp_api_keys(self) -> "ExploitIqSerpAPIWrapper": """Initialize API keys pool from SERPAPI_API_KEY environment variable. Parses comma-separated keys from the serpapi_api_key field (populated by parent class) diff --git a/src/vuln_analysis/utils/vex/implementations/csaf_generator.py b/src/vuln_analysis/utils/vex/implementations/csaf_generator.py index c5fe5f300..7078f7ece 100644 --- a/src/vuln_analysis/utils/vex/implementations/csaf_generator.py +++ b/src/vuln_analysis/utils/vex/implementations/csaf_generator.py @@ -23,7 +23,8 @@ from typing import Any, Dict from exploit_iq_commons.data_models.cve_intel import CveIntel -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from exploit_iq_commons.data_models.input import ExploitIqEngineInput +from vuln_analysis.data_models.state import ExploitIqEngineState from ..vex_generator_base import VexGenerator from ..vex_utils import build_oci_image_purl, get_vex_validator, build_patch_recommendation @@ -160,7 +161,7 @@ def _enrich_vulnerabilities_with_notes( "title": NOTE_TITLE_RHSA_STATEMENT }) - # Add ExploitIQ analysis summary + # Add ExploitIQ Analysis Summary summary = final_summaries.get(vuln_id) notes.append({ "category": NOTE_CATEGORY_OTHER, @@ -208,9 +209,9 @@ class CsafVexGenerator(VexGenerator): CSAF VEX generator. Builds a CSAF JSON document and validates it with the csaf-tool. """ - def generate(self, state: AgentMorpheusEngineState) -> Dict[str, Any]: + def generate(self, state: ExploitIqEngineState) -> Dict[str, Any]: - message: AgentMorpheusEngineInput = state.original_input + message: ExploitIqEngineInput = state.original_input csaf_gen = CSAFGenerator() diff --git a/src/vuln_analysis/utils/vex/tests/test_csaf_generator_integration.py b/src/vuln_analysis/utils/vex/tests/test_csaf_generator_integration.py index 4418a7663..f8bdf4161 100644 --- a/src/vuln_analysis/utils/vex/tests/test_csaf_generator_integration.py +++ b/src/vuln_analysis/utils/vex/tests/test_csaf_generator_integration.py @@ -23,17 +23,17 @@ from exploit_iq_commons.data_models.common import AnalysisType from exploit_iq_commons.data_models.cve_intel import CveIntel, CveIntelGhsa, CveIntelRhsa -from exploit_iq_commons.data_models.info import AgentMorpheusInfo, SBOMPackage +from exploit_iq_commons.data_models.info import ExploitIqInfo, SBOMPackage from exploit_iq_commons.data_models.input import ( - AgentMorpheusEngineInput, - AgentMorpheusInput, + ExploitIqEngineInput, + ExploitIqInput, ImageInfoInput, ManualSBOMInfoInput, ScanInfoInput, SourceDocumentsInfo, VulnInfo, ) -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState from vuln_analysis.utils.vex.implementations.csaf_generator import CsafVexGenerator from vuln_analysis.utils.vex.vex_generator_loader import load_vex_generator from vuln_analysis.utils.vex.vex_utils import build_oci_image_purl @@ -57,8 +57,8 @@ @pytest.fixture(scope="module") -def mock_state() -> AgentMorpheusEngineState: - """Fixture providing a default mock AgentMorpheusEngineState for testing. +def mock_state() -> ExploitIqEngineState: + """Fixture providing a default mock ExploitIqEngineState for testing. This state has one vulnerable CVE with a known affected status, with no GHSA or RHSA intel data. """ return create_mock_state() @@ -71,8 +71,8 @@ def create_mock_state( product_name: str = _DEFAULT_PRODUCT_NAME, product_tag: str = _DEFAULT_PRODUCT_TAG, sbom_packages: list[SBOMPackage] | None = _DEFAULT_SBOM_PACKAGES, -) -> AgentMorpheusEngineState: - """Create a mock AgentMorpheusEngineState for testing.""" +) -> ExploitIqEngineState: + """Create a mock ExploitIqEngineState for testing.""" intel = intel or [CveIntel(vuln_id=v) for v in vulns] @@ -86,15 +86,15 @@ def create_mock_state( sbom_info=sbom_info, ) - engine_input = AgentMorpheusEngineInput( - input=AgentMorpheusInput( + engine_input = ExploitIqEngineInput( + input=ExploitIqInput( scan=ScanInfoInput(vulns=[VulnInfo(vuln_id=v) for v in vulns]), image=image_info, ), - info=AgentMorpheusInfo(intel=intel), + info=ExploitIqInfo(intel=intel), ) - return AgentMorpheusEngineState( + return ExploitIqEngineState( cve_intel=intel, original_input=engine_input, final_summaries={v: _DEFAULT_SUMMARY.format(v=v) for v in vulns}, diff --git a/src/vuln_analysis/utils/vex/vex_generator_base.py b/src/vuln_analysis/utils/vex/vex_generator_base.py index fa8d3c452..215748ca0 100644 --- a/src/vuln_analysis/utils/vex/vex_generator_base.py +++ b/src/vuln_analysis/utils/vex/vex_generator_base.py @@ -18,7 +18,7 @@ from abc import ABC, abstractmethod from typing import Any, Dict -from vuln_analysis.data_models.state import AgentMorpheusEngineState +from vuln_analysis.data_models.state import ExploitIqEngineState class VexGenerator(ABC): @@ -27,7 +27,7 @@ class VexGenerator(ABC): """ @abstractmethod - def generate(self, state: AgentMorpheusEngineState) -> Dict[str, Any]: + def generate(self, state: ExploitIqEngineState) -> Dict[str, Any]: """ Generate a VEX document as a JSON-serializable dict from the engine state. """ diff --git a/tests/test_serp_api_key_rotation.py b/tests/test_serp_api_key_rotation.py index acc8a1108..a6d477e95 100644 --- a/tests/test_serp_api_key_rotation.py +++ b/tests/test_serp_api_key_rotation.py @@ -23,7 +23,7 @@ import pytest from aioresponses import aioresponses from aiohttp import ClientResponseError -from vuln_analysis.utils.serp_api_wrapper import MorpheusSerpAPIWrapper +from vuln_analysis.utils.serp_api_wrapper import ExploitIqSerpAPIWrapper SERPAPI_SEARCH_URL_PATTERN = re.compile(r'https://serpapi\.com/search\?.*') TEST_PAYLOAD = {"results": ["test"]} @@ -36,18 +36,18 @@ def serpapi_wrapper_single_key(): """Create a wrapper with a single API key.""" # Reset class-level state before each test - MorpheusSerpAPIWrapper._serp_api_keys = [] - MorpheusSerpAPIWrapper._serp_api_key_index = 0 - return MorpheusSerpAPIWrapper(serpapi_api_key=SINGLE_KEY) + ExploitIqSerpAPIWrapper._serp_api_keys = [] + ExploitIqSerpAPIWrapper._serp_api_key_index = 0 + return ExploitIqSerpAPIWrapper(serpapi_api_key=SINGLE_KEY) @pytest.fixture def serpapi_wrapper_two_keys(): """Create a wrapper with two API keys.""" # Reset class-level state before each test - MorpheusSerpAPIWrapper._serp_api_keys = [] - MorpheusSerpAPIWrapper._serp_api_key_index = 0 - return MorpheusSerpAPIWrapper(serpapi_api_key=TWO_KEYS) + ExploitIqSerpAPIWrapper._serp_api_keys = [] + ExploitIqSerpAPIWrapper._serp_api_key_index = 0 + return ExploitIqSerpAPIWrapper(serpapi_api_key=TWO_KEYS) @pytest.mark.asyncio @@ -124,9 +124,9 @@ async def test_key_rotation(error_code, serpapi_wrapper_two_keys): def test_concurrent_rotation(): """Test that concurrent key rotation is thread-safe.""" # Reset class-level state before test - MorpheusSerpAPIWrapper._serp_api_keys = [] - MorpheusSerpAPIWrapper._serp_api_key_index = 0 - wrapper = MorpheusSerpAPIWrapper(serpapi_api_key="key1,key2,key3") + ExploitIqSerpAPIWrapper._serp_api_keys = [] + ExploitIqSerpAPIWrapper._serp_api_key_index = 0 + wrapper = ExploitIqSerpAPIWrapper(serpapi_api_key="key1,key2,key3") num_threads = 10 iterations_per_thread = 5 diff --git a/tests/test_vex_csaf_helpers.py b/tests/test_vex_csaf_helpers.py index 687e52473..84a2ade9a 100644 --- a/tests/test_vex_csaf_helpers.py +++ b/tests/test_vex_csaf_helpers.py @@ -21,7 +21,8 @@ from exploit_iq_commons.data_models.cve_intel import CveIntel, CveIntelGhsa, CveIntelRhsa from vuln_analysis.utils.vex.implementations.csaf_generator import ( - _enrich_vulnerabilities_with_notes, + _enrich_vulnerabilities_with_notes, NOTE_TITLE_EXPLOITIQ_JUSTIFICATION_REASONING, NOTE_TITLE_EXPLOITIQ_SUMMARY, + NOTE_TITLE_EXPLOITIQ_JUSTIFICATION_LABEL, ) @@ -94,7 +95,7 @@ def test_adds_analysis_summary_note(self, base_csaf_json, base_intel_map, base_j _enrich_vulnerabilities_with_notes(base_csaf_json, base_intel_map, final_summaries, base_justifications) notes = base_csaf_json["vulnerabilities"][0]["notes"] - analysis_notes = [n for n in notes if n.get("title") == "ExploitIQ Analysis Summary"] + analysis_notes = [n for n in notes if n.get("title") == NOTE_TITLE_EXPLOITIQ_SUMMARY] assert len(analysis_notes) == 1 assert analysis_notes[0]["text"] == "This is the analysis summary" assert analysis_notes[0]["category"] == "other" @@ -112,11 +113,11 @@ def test_adds_justification_notes(self, base_csaf_json, base_intel_map, base_fin notes = base_csaf_json["vulnerabilities"][0]["notes"] - reasoning_notes = [n for n in notes if n.get("title") == "ExploitIQ Analysis Justification Reasoning"] + reasoning_notes = [n for n in notes if n.get("title") == NOTE_TITLE_EXPLOITIQ_JUSTIFICATION_REASONING] assert len(reasoning_notes) == 1 assert reasoning_notes[0]["text"] == "The vulnerable code path is reachable" - label_notes = [n for n in notes if n.get("title") == "ExploitIQ Analysis Justification Label"] + label_notes = [n for n in notes if n.get("title") == NOTE_TITLE_EXPLOITIQ_JUSTIFICATION_LABEL] assert len(label_notes) == 1 assert label_notes[0]["text"] == "vulnerable"