DSPM

Remediate your data security risks. Integrate with Prisma Cloud DSPM to fetch your data security risks and remediate them with OOTB playbooks.

Network Security · DSPM

Details

IDDSPM
ProviderPalo Alto Networks
CategoryNetwork Security
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658

README

Overview

The Prisma Cloud DSPM(Data Security Posture Management) integration enhances the management and remediation of DSPM risks. The integration provides users with actionable data, insights and a seamless workflow for addressing potential security threats.

Use Cases

  • Remediation of DSPM out-of-the-box risks based on automated playbooks.
  • Close or update risks by interacting with DSPM API using a dedicated list of building blocks.
  • Distribute DSPM risks to other systems.

Prerequisites

  1. An active Prisma Cloud DSPM account
  2. Slack V3 Pack
  3. AWS-S3 Pack
  4. Core REST APIs pack
  5. Atlassian Jira v3 Pack
  6. Google Cloud Storage Pack ( Optional )
  7. Azure Storage Container Pack ( Optional )

Configure Cortex XSOAR on Prisma Cloud DSPM

  1. Log in to you Prisma Cloud DSPM platform.
  2. Navigate to Settings > Workflow > XSOAR.
  3. Click Connect to create and configure a new XSOAR integration.
  4. XSOAR link - Add the XSOAR API URL.
  5. Notified On - Select the Risks option.
  6. Severity Threshold - Set the severity threshold to receive notifications for assets that fall under that severity.
  7. Filter By Tags - Notifications will be sent for assets that match any of the selected tags.
  8. Advanced - Add required headers Authorization and x-xdr-auth-id

Configure Prisma Cloud DSPM on Cortex XSOAR

  1. Navigate to Settings & Info > Settings > Integrations > Instances.
  2. Search for Prisma Cloud DSPM.
  3. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    DSPM server URL The tenant URL of the Prisma Cloud DSPM True
    DSPM API Key API key to use for the connection. True
    Trust any certificate (not secure)   False
    Use system proxy settings   False
    Fetch incidents   False
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

dspm-list-risk-findings


Retrieves risk findings matching the input criteria.

Base Command

dspm-list-risk-findings

Input

Argument Name Description Required
rule_name_in A comma-separated list of rule names. Optional
rule_name_equal The exact rule name. Optional
dspm_tag_key_in A comma-separated list of DSPM tag keys. Optional
dspm_tag_key_equal Exact DSPM tag key. Optional
dspm_tag_value_in A comma-separated list of DSPM tag values. Optional
dspm_tag_value_equal The exact DSPM tag value. Optional
projectId_in A comma-separated list of project IDs. Optional
projectId_equal The exact project ID. Optional
cloud_provider_in A comma-separated list of cloud providers. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Default is AWS. Optional
cloud_provider_equal The exact cloud provider. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
affects_in A comma-separated list of affects. Possible values are: SECURITY, COMPLIANCE, GOVERNANCE, SECURITY_AND_COMPLIANCE, SECURITY_AND_GOVERNANCE, COMPLIANCE_AND_GOVERNANCE, SECURITY_AND_COMPLIANCE_AND_GOVERNANCE. Optional
affects_equal The exact effect. Possible values are: SECURITY, COMPLIANCE, GOVERNANCE, SECURITY_AND_COMPLIANCE, COMPLIANCE_AND_GOVERNANCE, SECURITY_AND_GOVERNANCE, SECURITY_AND_COMPLIANCE_AND_GOVERNANCE. Optional
status_in A comma-separated list of statuses. Possible values are: OPEN, CLOSED, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Optional
status_equal The exact status. Possible values are: OPEN, CLOSED, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Optional
sort The sort order. Optional
limit The maximum number of risk findings to retrieve. Default is 50. Optional

Context Output

Path Type Description
DSPM.RiskFinding.asset Unknown The asset details associated with the risk finding.
DSPM.RiskFinding.cloudEnvironment String The cloud environment (public or private) associated with the risk finding.
DSPM.RiskFinding.cloudProvider String The cloud provider associated with the risk finding (e.g., AWS, Azure, GCP).
DSPM.RiskFinding.complianceStandards Unknown The compliance standards relevant to the risk finding.
DSPM.RiskFinding.firstDiscovered Date The date the risk finding was first discovered.
DSPM.RiskFinding.id String The unique ID of the risk finding.
DSPM.RiskFinding.projectId String The project ID where the asset resides.
DSPM.RiskFinding.ruleName String The rule name associated with the risk finding.
DSPM.RiskFinding.severity String The severity of the risk finding (e.g., Low, Medium, High).
DSPM.RiskFinding.status String The current status of the risk finding (e.g., Open, Closed).

Command example


#### Context Example

```json
{
  "DSPM": {
    "RiskFinding": [
      {
        "id": "00000000-0000-4f99-0000-616843b6b19e",
        "ruleName": "Empty storage asset",
        "severity": "LOW",
        "asset": {},
        "status": "OPEN",
        "projectId": "********",
        "cloudProvider": "AWS",
        "cloudEnvironment": "UNKNOWN",
        "firstDiscovered": "2024-09-27T11:55:39.059125Z",
        "complianceStandards": {}
    }
    ]
  }
}

Human Readable Output

Results

Asset Cloud Environment Cloud Provider Compliance Standards First Discovered ID Project ID Rule Name Severity Status
{} UNKNOWN AWS {} 2024-09-27T11:55:39.059125Z 00000000-0000-4f99-0000-616843b6b19e **** Empty storage asset LOW OPEN

dspm-get-risk-finding-by-id


Retrieves the details of a risk for the provided risk ID.

Base Command

dspm-get-risk-finding-by-id

Input

Argument Name Description Required
finding_id ID of the risk for which to retrieve details. Required

Context Output

Path Type Description
DSPM.RiskFinding.asset Unknown The asset details associated with the risk finding.
DSPM.RiskFinding.cloudEnvironment String The cloud environment (public or private) associated with the risk finding.
DSPM.RiskFinding.cloudProvider String The cloud provider associated with the risk finding (e.g., AWS, Azure, GCP).
DSPM.RiskFinding.complianceStandards Unknown The compliance standards relevant to the risk finding.
DSPM.RiskFinding.firstDiscovered Date The date the risk finding was first discovered.
DSPM.RiskFinding.id String The unique ID of the risk finding.
DSPM.RiskFinding.projectId String The project ID where the asset resides.
DSPM.RiskFinding.ruleName String The rule name associated with the risk finding.
DSPM.RiskFinding.severity String The severity of the risk finding (e.g., Low, Medium, High).
DSPM.RiskFinding.status String The current status of the risk finding (e.g., Open, Closed).

Command example

!dspm-get-risk-finding-by-id finding_id="00000000-0000-4f99-0000-616843b6b19e"

Context Example

{
    "DSPM": {
            "RiskFinding": {
            "id": "00000000-0000-4f99-0000-616843b6b19e",
            "ruleName": "Empty storage asset",
            "severity": "LOW",
            "asset": {},
            "status": "OPEN",
            "projectId": "********",
            "cloudProvider": "AWS",
            "cloudEnvironment": "UNKNOWN",
            "firstDiscovered": "2024-09-27T11:55:39.059125Z",
            "complianceStandards": {}
        }
    }
}

Human Readable Output

Results

Asset Cloud Environment Cloud Provider Compliance Standards First Discovered ID Project ID Rule Name Severity Status
{} UNKNOWN AWS {} 2024-09-27T11:55:39.059125Z 00000000-0000-4f99-0000-616843b6b19e **** Empty storage asset LOW OPEN

dspm-list-assets


Retrieves a list of assets for the company.

Base Command

dspm-list-assets

Input

Argument Name Description Required
region_in A comma-separated list of regions. Optional
region_equal The exact region. Optional
cloud_provider_in A comma-separated list of cloud providers. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
cloud_provider_equal The exact cloud provider. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
service_type_in A comma-separated list of service types. Optional
service_type_equal The exact service type. Optional
lifecycle_in A comma-separated list of life cycles. Possible values are: RUNNING, STOPPED, DELETED. Optional
lifecycle_equal The exact lifecycle. Possible values are: RUNNING, STOPPED, DELETED. Optional
sort The sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. Optional
limit The maximum number of assets to retrieve. Default is 50. Optional

Context Output

Path Type Description
DSPM.Asset.dataTypes Unknown Data types associated with the asset.
DSPM.Asset.dataTypeGroups Unknown Data type groups associated with the asset.
DSPM.Asset.assetDigTags Unknown Dig tags associated with the asset.
DSPM.Asset.cloudEnvironment String The cloud environment in which the asset exists.
DSPM.Asset.cloudProvider String The cloud provider for the asset.
DSPM.Asset.encrypted Boolean Indicates if the asset is encrypted.
DSPM.Asset.id String The unique identifier of the asset.
DSPM.Asset.lifecycle String Lifecycle status of the asset.
DSPM.Asset.name String The name of the asset.
DSPM.Asset.openAlertsCount Number The count of open alerts for the asset.
DSPM.Asset.openRisksCount Number The count of open risks for the asset.
DSPM.Asset.openToWorld Boolean Indicates if the asset is open to the world.
DSPM.Asset.projectId String The ID of the project associated with the asset.
DSPM.Asset.projectName String The name of the project associated with the asset.
DSPM.Asset.serviceType String The type of service associated with the asset.
DSPM.Asset.tags Unknown Tags related to the asset.

Command example

!dspm-list-assets cloudProviderEqual=AWS serviceTypeEqual=S3

Context Example

{
    "DSPM": {
        "Assets": [{
        "projectId": "************",
        "projectName": "************",
        "name": "dymmy-ci0jq3kgvjnccdfp-us-east-1",
        "cloudProvider": "AWS",
        "cloudEnvironment": "TESTING",
        "serviceType": "S3",
        "dataTypeGroups": [],
        "dataTypes": [],
        "lifecycle": "RUNNING",
        "openRisksCount": 0,
        "openAlertsCount": 0,
        "encrypted": true,
        "openToWorld": false,
        "tags": {},
        "assetDigTags": [],
        "id": "arn:aws:s3:::dymmy-ci0jq3kgvjnccdfp-us-east-1"
    }]
    }
}

Human Readable Output

Results

Asset Dig Tags Cloud Environment Cloud Provider Encrypted ID Lifecycle Name Open Alerts Count Open Risks Count Open To World Project ID Project Name Service Type Tags Data Type Groups Data Types
  TESTING AWS true arn:aws:s3:::dymmy-ci0jq3kgvjnccdfp-us-east-1 RUNNING dymmy-ci0jq3kgvjnccdfp-us-east-1 0 0 false **** **** S3      

dspm-get-asset-details


Retrieves details for the specified asset ID.

Base Command

dspm-get-asset-details

Input

Argument Name Description Required
asset_id ID of the asset for which to retrieve details. Required

Context Output

Path Type Description
DSPM.AssetDetails.assetDigTags Unknown The dig tags associated with the asset.
DSPM.AssetDetails.cloudEnvironment String The cloud environment in which the asset exists.
DSPM.AssetDetails.cloudProvider String The cloud provider for the asset (e.g., AWS, Azure, GCP).
DSPM.AssetDetails.dataTypeGroups Unknown Groups of data types associated with the asset.
DSPM.AssetDetails.dataTypes Unknown The data types related to the asset.
DSPM.AssetDetails.encrypted Boolean Indicates if the asset is encrypted.
DSPM.AssetDetails.id String The unique identifier of the asset.
DSPM.AssetDetails.lifecycle String The lifecycle status of the asset.
DSPM.AssetDetails.name String The name of the asset.
DSPM.AssetDetails.openAlertsCount Number The count of open alerts for the asset.
DSPM.AssetDetails.openRisksCount Number The count of open risks for the asset.
DSPM.AssetDetails.openToWorld Boolean Indicates if the asset is open to the world.
DSPM.AssetDetails.projectId String The ID of the project associated with the asset.
DSPM.AssetDetails.projectName String The name of the project associated with the asset.
DSPM.AssetDetails.serviceType String The type of service associated with the asset.
DSPM.AssetDetails.tags Unknown Tags related to the asset.

Command example

!dspm-get-asset-details asset_id="arn:aws:s3:::dummyS3-cifp-us-east-1"

Context Example

{
    "DSPM": {
        "AssetDetails": {
            "assetDigTags": [],
            "cloudEnvironment": "TESTING",
            "cloudProvider": "AWS",
            "dataTypeGroups": [],
            "dataTypes": [],
            "encrypted": true,
            "id": "arn:aws:s3:::dummyS3-cifp-us-east-1",
            "lifecycle": "RUNNING",
            "name": "dymmy-ci0jq3kgvjnccdfp-us-east-1",
            "openAlertsCount": 0,
            "openRisksCount": 0,
            "openToWorld": false,
            "projectId": "************",
            "projectName": "************",
            "serviceType": "S3",
            "tags": {}
        }
    }
}

Human Readable Output

Results

assetDigTags cloudEnvironment cloudProvider dataTypeGroups dataTypes encrypted id lifecycle name openAlertsCount openRisksCount openToWorld projectId projectName serviceType tags
  TESTING AWS     true arn:aws:s3:::dummyS3-cifp-us-east-1 RUNNING dymmy-ci0jq3kgvjnccdfp-us-east-1 0 0 false **** **** S3  

dspm-get-asset-files-by-id


Retrieves file details for the specified asset ID.

Base Command

dspm-get-asset-files-by-id

Input

Argument Name Description Required
asset_id ID of the asset for which to retrieve file details. Required

Context Output

Path Type Description
DSPM.AssetFiles.filename String Asset file name.
DSPM.AssetFiles.path String Asset file path.
DSPM.AssetFiles.type String Asset file type.
DSPM.AssetFiles.size String Asset file size.
DSPM.AssetFiles.openToWorld Boolean Whether the asset is open to world.
DSPM.AssetFiles.isDeleted Boolean Whether the asset is deleted.
DSPM.AssetFiles.isMalicious Boolean Whether the asset is malicious.
DSPM.AssetFiles.dataTypes.name String Asset file data types name.
DSPM.AssetFiles.dataTypes.label String Asset file data types label.
DSPM.AssetFiles.dataTypes.count Number Asset file data types count.
DSPM.AssetFiles.dataTypes.valueDetails.masked_value String Asset file data types value detail masked value.
DSPM.AssetFiles.dataTypes.valueDetails.line Number Asset file data types value detail line.
DSPM.AssetFiles.labels String Asset file labels.
DSPM.AssetFiles.isDbDump Boolean Asset file is a database dump.

Command example

!dspm-get-asset-files-by-id asset_id="arn:aws:s3:::dummyS3-cifp-us-east-1"

Context Example

{
    "files": [
        {
            "filename": "268d4e2d-03f2-4044-b82d-8855b2e77f8d.csv",
            "path": "268d4e2d-03f2-4044-b82d-8855b2e77f8d.csv",
            "type": "Data Format",
            "size": "17081",
            "openToWorld": true,
            "isDeleted": false,
            "isMalicious": false,
            "dataTypes": [
                {
                    "name": "IP Address",
                    "label": "Sensitive",
                    "count": 100,
                    "valueDetails": [
                        {
                            "masked_value": "20.163.*.*",
                            "line": 3
                        },
                        {
                            "masked_value": "38.229.*.*",
                            "line": 4
                        },
                        {
                            "masked_value": "45.136.*.*",
                            "line": 5
                        }
                    ]
                },
                {
                    "name": "Internal IP Address",
                    "label": "Sensitive",
                    "count": 100,
                    "valueDetails": [
                        {
                            "masked_value": "10.0.*.*",
                            "line": 2
                        },
                        {
                            "masked_value": "10.0.*.*",
                            "line": 8
                        },
                        {
                            "masked_value": "10.0.*.*",
                            "line": 14
                        }
                    ]
                }
            ],
            "labels": [
                "Sensitive"
            ],
            "isDbDump": false
        },
        {
            "filename": "data security test cases.pdf",
            "path": "data security test cases.pdf",
            "type": "Document",
            "size": "73286",
            "openToWorld": true,
            "isDeleted": false,
            "isMalicious": false,
            "dataTypes": [
                {
                    "name": "Street Address",
                    "label": "Sensitive",
                    "count": 1,
                    "valueDetails": [
                        {
                            "masked_value": "3** E*** R******* Street",
                            "line": null
                        }
                    ]
                },
                {
                    "name": "Email Address",
                    "label": "PII",
                    "count": 1,
                    "valueDetails": [
                        {
                            "masked_value": "t**t@b****l.com",
                            "line": null
                        }
                    ]
                }
            ],
            "labels": [
                "PII",
                "Sensitive"
            ],
            "isDbDump": false
        }
    ],
    "filesCount": 2
}

Human Readable Output

filename path type size openToWorld isDeleted isMalicious dataTypes labels isDbDump
268d4e2d-03f2-4044-b82d-8855b2e77f8d.csv 268d4e2d-03f2-4044-b82d-8855b2e77f8d.csv Data Format 17081 true false false IP Address (Sensitive), Internal IP Address (Sensitive) Sensitive false
data security test cases.pdf data security test cases.pdf Document 73286 true false false Street Address (Sensitive), Email Address (PII) PII, Sensitive false

dspm-get-list-of-asset-fields-by-id


Return list of fields for structured assets such as RDS, Aurora, and BigQuery.

Base Command

dspm-get-list-of-asset-fields-by-id

Input

Argument Name Description Required
assetId ID of the asset for which to retrieve field details. Required

Context Output

Path Type Description
DSPM.AssetFields.name String Asset field name.
DSPM.AssetFields.path String Asset field path.
DSPM.AssetFields.tableName String Asset field table name.
DSPM.AssetFields.tableSize String Asset field table size.
DSPM.AssetFields.databaseName String Asset field database name.
DSPM.AssetFields.collectionName String Asset field collection name.
DSPM.AssetFields.type String Asset field type.
DSPM.AssetFields.dataTypes.name String Asset field data type name.
DSPM.AssetFields.dataTypes.label String Asset field data type label.
DSPM.AssetFields.dataTypes.hitPercentage Number Asset field data type hit percentage.
DSPM.AssetFields.dataTypes.maskedValues.masked_value String Asset field datat ype masked value.
DSPM.AssetFields.dataTypes.maskedValues.line Number Asset field data type masked value line.
DSPM.AssetFields.schemaName String Asset field schema name.

Command example

!dspm-get-list-of-asset-fields-by-id assetId="arn:aws:rds:::dummyrds-cifp-us-east-1"

Context Example

{
    "fields": [
        {
            "name": "maidenname",
            "dataTypes": [],
            "path": "/public/dummy",
            "tableName": "dummy",
            "tableSize": "29996",
            "databaseName": "Hi",
            "collectionName": null,
            "type": "varchar",
            "schemaName": "public"
        },
        {
            "name": "phone",
            "dataTypes": [],
            "path": "/public/dummy",
            "tableName": "dummy",
            "tableSize": "29996",
            "databaseName": "Hi",
            "collectionName": null,
            "type": "varchar",
            "schemaName": "public"
        }
    ],
    "fieldsCount": 2
}

Human Readable Output

Asset Fields

name dataTypes path tableName tableSize databaseName collectionName type schemaName
maidenname [] /public/dummy dummy 29996 Hi null varchar public
maidenname [] /public/dummy dummy 29996 Hi null varchar public

dspm-get-data-types


Fetches the available data types for the DSPM integration.

Base Command

dspm-get-data-types

Input

There are no input arguments for this command.

Context Output

Path Type Description
DSPM.DataTypes.Key String Data types key.
DSPM.DataTypes.No Number Data types number.

Command example


#### Context Example

```json
{
    "DSPM": {
        "DataTypes": [
            {
                "Key": "ID Number - Aadhaar (India)",
                "No": 1
            },
            {
                "Key": "Artifactory API Key",
                "No": 2
            },
            {
                "Key": "AWS Secret Key",
                "No": 3
            },
            {
                "Key": "Credit Card Expiration Date",
                "No": 4
            },
            {
                "Key": "Certificate",
                "No": 5
            }
        ]
    }
}

Human Readable Output

Data Types

No Key
1 ID Number - Aadhaar (India)
2 Artifactory API Key
3 AWS Secret Key
4 Credit Card Expiration Date
5 Certificate

dspm-list-labels


Returns a list of label names based on the company.

Base Command

dspm-list-labels

Input

There are no input arguments for this command.

Context Output

Path Type Description
DSPM.Label.Key String Label key.
DSPM.Label.No unknown Label number.

Command example


#### Context Example

```json
{
    "DSPM": {
        "Label": [
            {
                "Key": "PCI",
                "No": 1
            },
            {
                "Key": "PHI",
                "No": 2
            },
            {
                "Key": "PII",
                "No": 3
            },
            {
                "Key": "Confidential",
                "No": 4
            },
            {
                "Key": "Sensitive",
                "No": 5
            }
        ]
    }
}

Human Readable Output

Data Types

No Key
1 PCI
2 PHI
3 PII
4 Confidential
5 Sensitive

dspm-list-data-types-findings


Retrieves a list of data type findings for the company.

Base Command

dspm-list-data-types-findings

Input

Argument Name Description Required
region_in A comma-separated list of regions. Optional
region_equal The exact region. Optional
cloud_provider_in A comma-separated list of cloud providers. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
cloud_provider_equal The exact cloud provider. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
service_type_in A comma-separated list of service types. Optional
service_type_equal The exact service type. Optional
lifecycle_in A comma-separated list of life cycles. Optional
projectId_in A comma-separated list of project IDs. Optional
projectId_equal The exact project ID. Optional
lifecycle_equal The exact life cycle. Optional
sort The sorting criteria in the format: property,(asc|desc). Default sort order is ascending. Multiple sort criteria are supported. Optional
limit The maximum number of data types findings to retrieve. Default is 50. Optional

Context Output

Path Type Description
DSPM.DataTypesFinding.dataTypeName String Represents the name of the data type being analyzed.
DSPM.DataTypesFinding.label String Label associated with the data type, such as PII.
DSPM.DataTypesFinding.records Integer The number of records associated with the data type.
DSPM.DataTypesFinding.publicRecords Integer The number of public records found for this data type.
DSPM.DataTypesFinding.assets Integer The number of assets associated with this data type.
DSPM.DataTypesFinding.clouds String The clouds where the data type was found (e.g., AWS, Azure).
DSPM.DataTypesFinding.regions String The regions where the data type was found.
DSPM.DataTypesFinding.lastFound Date The timestamp when the data type was last found.
DSPM.DataTypesFinding.recordsAtRisk.high Integer The number of high-risk records found for this data type.
DSPM.DataTypesFinding.recordsAtRisk.medium Integer The number of medium-risk records found for this data type.
DSPM.DataTypesFinding.recordsAtRisk.low Integer The number of low-risk records found for this data type.

Command example

!dspm-list-data-types-findings cloudProviderEqual=AWS

Context Example

[{
        "dataTypeName": "AADHAAR_INDIVIDUAL_IDENTIFICATION",
        "label": "PII",
        "records": 4,
        "publicRecords": 0,
        "assets": 1,
        "clouds": [
            "AWS"
        ],
        "regions": [
            "us-east-1"
        ],
        "lastFound": "2024-05-09T03:24:29Z",
        "recordsAtRisk": {
            "high": 0,
            "medium": 4,
            "low": 0
        }
}]

Human Readable Output

Data Types Findings

dataTypeName label records publicRecords assets clouds regions lastFound recordsAtRisk.high recordsAtRisk.medium recordsAtRisk.low
AADHAAR_INDIVIDUAL_IDENTIFICATION PII 4 0 1 AWS us-east-1 2024-05-09T03:24:29Z 0 4 0

dspm-update-risk-finding-status


Updates the status of a risk finding.

Base Command

dspm-update-risk-finding-status

Input

Argument Name Description Required
risk_finding_id Risk Finding ID. Required
status List of supported statuses. Possible values are: OPEN, CLOSED, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Required

Context Output

Path Type Description
DSPM.RiskFindingStatusUpdate.newStatus String Updated risk finding status.
DSPM.RiskFindingStatusUpdate.oldStatus String Old risk finding status.
DSPM.RiskFindingStatusUpdate.riskFindingId String Risk finding ID.

Command example

!dspm-update-risk-finding-status riskFindingId="00000000-0000-4f99-0000-616843b6b19e" status=INVESTIGATING

Context Example

{
    "DSPM": {
        "RiskFindingStatusUpdate": {
            "newStatus": "INVESTIGATING",
            "oldStatus": "INVESTIGATING",
            "riskFindingId": "00000000-0000-4f99-0000-616843b6b19e"
        }
    }
}

Human Readable Output

Risk Status Update

Risk Finding ID Old Status New Status
00000000-0000-4f99-0000-616843b6b19e INVESTIGATING INVESTIGATING

dspm-update-alert-status


Updates the status of an alert.

Base Command

dspm-update-alert-status

Input

Argument Name Description Required
alert_id Alert ID. Required
status List of supported statuses. Possible values are: OPEN, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Required

Context Output

Path Type Description
DSPM.AlertStatusUpdate.newStatus String Updated alert status.
DSPM.AlertStatusUpdate.oldStatus String Old alert status.
DSPM.AlertStatusUpdate.alertId String Alert ID.

Command example

!dspm-update-alert-status alertId="000000608" status=INVESTIGATING

Context Example

{
    "DSPM": {
        "AlertStatusUpdate": {
            "newStatus": "INVESTIGATING",
            "oldStatus": "INVESTIGATING",
            "alertId": "000000608"
        }
    }
}

Human Readable Output

Alert Status Update

Alert ID Old Status New Status
000000608 INVESTIGATING INVESTIGATING

dspm-list-alerts


Fetch list of alerts.

Base Command

dspm-list-alerts

Input

Argument Name Description Required
detection_time_equals The exact detection time (equals). detection time format - YYYY-MM-DDTHH:MM:SSZ. Optional
detection_time_greater_than_or_equal Detection time (greater than or equal). detection time format - YYYY-MM-DDTHH:MM:SSZ. Optional
detection_time_greater_than Detection time (greater than). detection time format - YYYY-MM-DDTHH:MM:SSZ. Optional
detection_time_less_than_or_equal Detection time (less than or equal). detection time format - YYYY-MM-DDTHH:MM:SSZ. Optional
detection_time_less_than Detection time (less than). detection time format - YYYY-MM-DDTHH:MM:SSZ. Optional
policy_name_in A comma-separated list of policy names. Optional
policy_name_equals The exact policy name. Optional
asset_name_in A comma-separated list of asset names. Optional
asset_name_equals The exact asset name. Optional
cloud_provider_in A comma-separated list of cloud providers. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
cloud_provider_equals The exact cloud provider. Possible values are: AWS, AZURE, GCP, SNOWFLAKE, FILE_SHARE, O365. Optional
destination_project_vendor_name_in A comma-separated list of project vendor names. Optional
destination_project_vendor_name_equals The exact destination project vendor name. Optional
cloud_environment_in A comma-separated list of cloud environments. Possible values are: UNKNOWN, DEVELOPMENT, STAGING, TESTING, PRODUCTION. Optional
cloud_environment_equals The exact cloud environment. Possible values are: UNKNOWN, DEVELOPMENT, STAGING, TESTING, PRODUCTION. Optional
policy_severity_in A comma-separated list of policy severities. Possible values are: HIGH, MEDIUM, LOW. Optional
policy_severity_equals The exact policy severity. Possible values are: HIGH, MEDIUM, LOW. Optional
category_type_in A comma-separated list of category types. Possible values are: FIRST_MOVE, ATTACK, COMPLIANCE, ASSET_AT_RISK, RECONNAISSANCE. Optional
category_type_equals The exact category type. Possible values are: FIRST_MOVE, ATTACK, COMPLIANCE, ASSET_AT_RISK, RECONNAISSANCE. Optional
status_in A comma-separated list of statuses. Possible values are: OPEN, CLOSED, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Optional
status_equals The exact status. Possible values are: OPEN, CLOSED, UNIMPORTANT, WRONG, HANDLED, INVESTIGATING. Optional
sort Sort order (property,asc|desc). Optional
limit The maximum number of alerts to retrieve. Default is 50. Optional

Context Output

Path Type Description
DSPM.Alert.id String Alert ID.
DSPM.Alert.detectionTime Date Alert detection time.
DSPM.Alert.policyName String Alert policy name.
DSPM.Alert.assetName String Alert asset name.
DSPM.Alert.assetLabels Unknown Alert asset label.
DSPM.Alert.cloudProvider String Alert cloud provider.
DSPM.Alert.destinationProjects Unknown Alert destination projects.
DSPM.Alert.cloudEnvironment String Alert cloud enviroment.
DSPM.Alert.policySeverity String Alert policy severity.
DSPM.Alert.policyCategoryType String Alert policy category type.
DSPM.Alert.status String Alert status.
DSPM.Alert.eventActor String Alert event actor.
DSPM.Alert.eventUserAgent String Alert event user agent.
DSPM.Alert.eventActionMedium String Alert event action medium.
DSPM.Alert.eventSource String Alert event source.
DSPM.Alert.policyFrameWorks String Alert policy frameworks.
DSPM.Alert.eventRawData String Alert event raw data.

Command example

!dspm-list-alerts cloudEnvironmentEquals="TESTING"

Context Example

{
    "DSPM": {
        "Alerts": [
    {
        "id": "340256006",
        "detectionTime": "2024-08-07T18:55:50.64996Z",
        "policyName": "Asset made public",
        "assetName": "mikeys3",
        "assetLabels": [],
        "cloudProvider": "AWS",
        "destinationProjects": {},
        "cloudEnvironment": "TESTING",
        "policySeverity": "HIGH",
        "policyCategoryType": "ATTACK",
        "status": "OPEN",
        "eventActor": "dummy_email",
        "eventUserAgent": "[S3Console/0.4, aws-internal/3 aws-sdk-java/1.12.488 Linux/5.10.220-187.867.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.372-b08 java/1.8.0_372 vendor/Oracle_Corporation cfg/retry-mode/standard]",
        "eventActionMedium": "CONSOLE",
        "eventSource": "***.**.**.***.***",
        "policyFrameWorks": [
            "MITRE-T1098"
        ],
        "eventRawData": "{\"eventVersion\":\"1.09\",\"userIdentity\":{\"type\":\"AssumedRole\",\"principalId\":\"AROASI3QR4HKUAIEPBICG:dummy_email\",\"arn\":\"arn:aws:sts::576847873638:assumed-role/sso_admin-tac-nam/dummy_email\",\"accountId\":\"576847873638\",\"accessKeyId\":\"ASIASI3QR4HK2LDI5JMN\",\"sessionContext\":{\"sessionIssuer\":{\"type\":\"Role\",\"principalId\":\"AROASI3QR4HKUAIEPBICG\",\"arn\":\"arn:aws:iam::576847873638:role/sso_admin-tac-nam\",\"accountId\":\"576847873638\",\"userName\":\"sso_admin-tac-nam\"},\"attributes\":{\"creationDate\":\"2024-08-07T18:51:51Z\",\"mfaAuthenticated\":\"false\"}}},\"eventTime\":\"2024-08-07T18:55:37Z\",\"eventSource\":\"s3.amazonaws.com\",\"eventName\":\"PutBucketPolicy\",\"awsRegion\":\"us-east-1\",\"sourceIPAddress\":\"***.**.**.***.***\",\"userAgent\":\"[S3Console/0.4, aws-internal/3 aws-sdk-java/1.12.488 Linux/5.10.220-187.867.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.372-b08 java/1.8.0_372 vendor/Oracle_Corporation cfg/retry-mode/standard]\",\"requestParameters\":{\"bucketPolicy\":{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"Statement1\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListBucketMultipartUploads\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::mikeys3\",\"arn:aws:s3:::mikeys3/*\"]}]},\"bucketName\":\"mikeys3\",\"Host\":\"s3.amazonaws.com\",\"policy\":\"\"},\"responseElements\":null,\"additionalEventData\":{\"SignatureVersion\":\"SigV4\",\"CipherSuite\":\"TLS_AES_128_GCM_SHA256\",\"bytesTransferredIn\":568,\"AuthenticationMethod\":\"AuthHeader\",\"x-amz-id-2\":\"KXHYo+o2L/Gnk0pmKY+gV+0YufF6uGyD3GRwK+FXEJ7eai772ytOzbV9CwwoezhB5PPR/6RxZyhOyBowBOyQih\",\"bytesTransferredOut\":0},\"requestID\":\"CJ3J7M851NAGAF58\",\"eventID\":\"df06b9ad-79dc-4a17-ae0e-82ecff9cfa5e\",\"readOnly\":false,\"resources\":[{\"accountId\":\"576847873638\",\"type\":\"AWS::S3::Bucket\",\"ARN\":\"arn:aws:s3:::mikeys3\"}],\"eventType\":\"AwsApiCall\",\"managementEvent\":true,\"recipientAccountId\":\"576847873638\",\"vpcEndpointId\":\"vpce-f40dc59d\",\"eventCategory\":\"Management\",\"tlsDetails\":{\"tlsVersion\":\"TLSv1.3\",\"cipherSuite\":\"TLS_AES_128_GCM_SHA256\",\"clientProvidedHostHeader\":\"s3.amazonaws.com\"}}"
    }
   ]
    }
}

Human Readable Output

DSPM Alert

Alert ID Detection Time Policy Name Asset Name Cloud Provider Cloud Environment Policy Severity Policy Category Status Event Actor Event Action Medium Event Source Policy Frameworks eventRawData
340256006 2024-08-07T18:55:50.64996Z Asset made public mikeys3 AWS TESTING HIGH ATTACK OPEN dummy_email CONSOLE .**.**..*** MITRE-T1098 ”{"eventVersion":"1.09","userIdentity":{"type":"AssumedRole","principalId":"AROASI3QR4HKUAIEPBICG:dummy_email","arn":"arn:aws:sts::576847873638:assumed-role/sso_admin-tac-nam/dummy_email","accountId":"576847873638","accessKeyId":"ASIASI3QR4HK2LDI5JMN","sessionContext":{"sessionIssuer":{"type":"Role","principalId":"AROASI3QR4HKUAIEPBICG","arn":"arn:aws:iam::576847873638:role/sso_admin-tac-nam","accountId":"576847873638","userName":"sso_admin-tac-nam"},"attributes":{"creationDate":"2024-08-07T18:51:51Z","mfaAuthenticated":"false"}}},"eventTime":"2024-08-07T18:55:37Z","eventSource":"s3.amazonaws.com","eventName":"PutBucketPolicy","awsRegion":"us-east-1","sourceIPAddress":".**.**..**","userAgent":"[S3Console/0.4, aws-internal/3 aws-sdk-java/1.12.488 Linux/5.10.220-187.867.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.372-b08 java/1.8.0_372 vendor/Oracle_Corporation cfg/retry-mode/standard]","requestParameters":{"bucketPolicy":{"Version":"2012-10-17","Statement":[{"Sid":"Statement1","Effect":"Allow","Principal":"","Action":["s3:AbortMultipartUpload","s3:DeleteObject","s3:GetObject","s3:ListBucketMultipartUploads","s3:ListMultipartUploadParts","s3:PutObject"],"Resource":["arn:aws:s3:::mikeys3","arn:aws:s3:::mikeys3/*"]}]},"bucketName":"mikeys3","Host":"s3.amazonaws.com","policy":""},"responseElements":null,"additionalEventData":{"SignatureVersion":"SigV4","CipherSuite":"TLS_AES_128_GCM_SHA256","bytesTransferredIn":568,"AuthenticationMethod":"AuthHeader","x-amz-id-2":"KXHYo+o2TWL/Gnk0pmKY+gV+0YufF6uGyD3GRwK+FXEJ7eai772ytOzbV9CwwoBq+pezhB5PPR/6RxZyhOyZltIBowBOyQih","bytesTransferredOut":0},"requestID":"CJ3J7M851NAGAF58","eventID":"df06b9ad-79dc-4a17-ae0e-82ecff9cfa5e","readOnly":false,"resources":[{"accountId":"576847873638","type":"AWS::S3::Bucket","ARN":"arn:aws:s3:::mikeys3"}],"eventType":"AwsApiCall","managementEvent":true,"recipientAccountId":"576847873638","vpcEndpointId":"vpce-f40dc59d","eventCategory":"Management","tlsDetails":{"tlsVersion":"TLSv1.3","cipherSuite":"TLS_AES_128_GCM_SHA256","clientProvidedHostHeader":"s3.amazonaws.com"}}”

Configuration parameters

  • dspmBaseUrl — Your DSPM server URL (required)
  • dspmApiKey — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents

Commands (12)

  • dspm-get-asset-details

    Retrieves details for the specified asset ID.

  • dspm-get-asset-files-by-id

    Retrieves file details for the specified asset ID.

  • dspm-get-data-types

    Fetches the available data types for the DSPM integration.

  • dspm-get-list-of-asset-fields-by-id

    Return list of fields for structured assets such as RDS, Aurora, and BigQuery.

  • dspm-get-risk-finding-by-id

    Retrieves the details of a risk for the provided risk ID.

  • dspm-list-alerts

    Fetch list of alerts.

  • dspm-list-assets

    Retrieves a list of assets for the company.

  • dspm-list-data-types-findings

    Retrieves a list of data type findings for the company.

  • dspm-list-labels

    Returns a list of label names based on the company.

  • dspm-list-risk-findings

    Retrieves risk findings matching the input criteria.

  • dspm-update-alert-status

    Updates the status of an alert.

  • dspm-update-risk-finding-status

    Updates the status of a risk finding.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
from CommonServerUserPython import *  # noqa: F401

import urllib3
from typing import Any
import re


# Disable insecure warnings
urllib3.disable_warnings()
""" CONSTANTS """
MAX_PAGE_SIZE: int = 50
DEFAULT_LIMIT: str = "50"
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR
GET_RISK_FINDINGS_ENDPOINT = "/v1/risk-findings"
GET_ASSET_LISTS = "/v1/assets"
GET_ASSET_DETAILS = "/v1/assets/id?id="
GET_ASSET_FILES = "/v1/classification/asset-files/id"
GET_ASSET_FIELDS = "/v1/classification/asset-fields/id"
GET_DATA_TYPES_ENDPOINT: str = "/v1/classification/data-types"
GET_DATA_TYPE_FINDINGS_ENDPOINT: str = "/v1/data-type-findings"
GET_ALERTS_LIST: str = "/v1/alerts"
GET_LABELS: str = "/v1/labels"
GET_RISK_FINDING_BY_ID: str = "/v1/risk-findings/id/"

SUPPORTED_CLOUD_PROVIDERS = ["AWS", "AZURE", "GCP", "SNOWFLAKE", "FILE_SHARE", "O365"]
SUPPORTED_CATEGORIES = [
    "SECURITY",
    "COMPLIANCE",
    "GOVERNANCE",
    "SECURITY_AND_COMPLIANCE",
    "SECURITY_AND_GOVERNANCE",
    "COMPLIANCE_AND_GOVERNANCE",
    "SECURITY_AND_COMPLIANCE_AND_GOVERNANCE",
]
SUPPORTED_SERVICE_TYPES = [
    "ATHENA",
    "AURORA",
    "AWS_BACKUP",
    "DOCUMENTDB",
    "DYNAMODB",
    "DAX",
    "EMR",
    "EBS",
    "EFS",
    "ELASTICACHE",
    "FSX",
    "KINESIS_DELIVERY_STREAM",
    "MEMORYDB",
    "NEPTUNE",
    "QLDB",
    "RDS",
    "REDSHIFT",
    "S3",
    "TIMESTREAM",
    "OPENSEARCH",
    "COSMOS_DB",
    "SYNAPSE",
    "CACHE_FOR_REDIS",
    "MARIA_DB",
    "MYSQL_SERVER",
    "POSTGRESQL_SERVER",
    "SQL_SERVER",
    "STORAGE_ACCOUNT",
    "SQL_MANAGED_INSTANCE",
    "ANF",
    "CLOUD_STORAGE",
    "CLOUD_SQL",
    "BIG_QUERY",
    "FILE_STORE",
    "CLOUD_SPANNER",
    "MEMORY_STORE",
    "BIG_TABLE",
    "FIRE_STORE",
    "UNMANAGED_AWS_MYSQL",
    "UNMANAGED_AWS_ORACLE_SERVER",
    "UNMANAGED_AWS_MONGO_DB",
    "UNMANAGED_AWS_POSTGRESQL",
    "UNMANAGED_AWS_REDIS",
    "UNMANAGED_AWS_SQLITE",
    "UNMANAGED_AWS_MS_SQL",
    "UNMANAGED_AWS_MARIA_DB",
    "UNMANAGED_AWS_NEO4_J",
    "UNMANAGED_AWS_ELASTIC",
    "UNMANAGED_AWS_COCKROACH_DB",
    "UNMANAGED_AWS_AEROSPIKE",
    "UNMANAGED_AWS_SCYLLA_DB",
    "UNMANAGED_AZURE_MYSQL",
    "UNMANAGED_AZURE_ORACLE_SERVER",
    "UNMANAGED_AZURE_MONGO_DB",
    "UNMANAGED_AZURE_POSTGRESQL",
    "UNMANAGED_AZURE_REDIS",
    "UNMANAGED_AZURE_SQLITE",
    "UNMANAGED_AZURE_MS_SQL",
    "UNMANAGED_AZURE_MARIA_DB",
    "UNMANAGED_AZURE_NEO4_J",
    "UNMANAGED_AZURE_ELASTIC",
    "UNMANAGED_AZURE_COCKROACH_DB",
    "UNMANAGED_AZURE_AEROSPIKE",
    "UNMANAGED_AZURE_SCYLLA_DB",
    "UNMANAGED_GCP_MYSQL",
    "UNMANAGED_GCP_ORACLE_SERVER",
    "UNMANAGED_GCP_MONGO_DB",
    "UNMANAGED_GCP_POSTGRESQL",
    "UNMANAGED_GCP_REDIS",
    "UNMANAGED_GCP_SQLITE",
    "UNMANAGED_GCP_MS_SQL",
    "UNMANAGED_GCP_MARIA_DB",
    "UNMANAGED_GCP_NEO4_J",
    "UNMANAGED_GCP_ELASTIC",
    "UNMANAGED_GCP_COCKROACH_DB",
    "UNMANAGED_GCP_AEROSPIKE",
    "UNMANAGED_GCP_SCYLLA_DB",
    "SNOWFLAKE_DB",
    "FILE_SHARE",
    "ONE_DRIVE",
    "SHARE_POINT",
    "AZURE_OPEN_AI_DEPLOYMENT",
    "VERTEX_ENDPOINT",
]
SUPPORTED_LIFECYCLE = ["RUNNING", "STOPPED", "DELETED"]
SUPPORTED_STATUSES = [
    "OPEN",
    "CLOSED",
    "UNIMPORTANT",
    "WRONG",
    "HANDLED",
    "INVESTIGATING",
]
SUPPORTED_CLOUD_ENVIRONMENTS = ["UNKNOWN", "DEVELOPMENT", "STAGING", "TESTING", "PRODUCTION"]
SUPPORTED_POLICY_SEVERITIES = ["HIGH", "MEDIUM", "LOW"]
SUPPORTED_CATEGORY_TYPES = ["FIRST_MOVE", "ATTACK", "COMPLIANCE", "ASSET_AT_RISK", "RECONNAISSANCE"]

""" CLIENT CLASS """


class Client(BaseClient):
    """Client class to interact with the service API"""

    def __init__(self, base_url, api_key, verify=True, proxy=False):
        headers = {"dig-api-key": api_key, "Accept": "application/json"}
        super().__init__(base_url, verify=verify, headers=headers, proxy=proxy)

    def get_labels(self):
        return self._http_request(method="GET", url_suffix=f"{GET_LABELS}")

    def fetch_risk_findings(self, params: dict[str, Any]):
        demisto.debug(f"all params : {params}")
        return self._http_request(method="GET", url_suffix=f"{GET_RISK_FINDINGS_ENDPOINT}", params=params)

    def get_asset_files(self, params: dict[str, Any]):
        return self._http_request(method="POST", url_suffix=f"{GET_ASSET_FILES}", params=params)

    def get_asset_details(self, asset_id: str):
        return self._http_request(method="GET", url_suffix=f"{GET_ASSET_DETAILS}{asset_id}")

    def get_asset_lists(self, params: dict[str, Any]):
        return self._http_request(method="GET", url_suffix=f"{GET_ASSET_LISTS}", params=params)

    def get_data_types(self):
        return self._http_request(
            method="GET",
            url_suffix=f"{GET_DATA_TYPES_ENDPOINT}",
        )

    def get_data_type_findings(self, params: dict[str, Any]):
        return self._http_request(method="GET", url_suffix=f"{GET_DATA_TYPE_FINDINGS_ENDPOINT}", params=params)

    def get_risk_information(self, risk_id: str):
        """
        Retrieve a risk finding by its ID from Dig Security.

        :param incident_id: The ID of the incident to retrieve.
        :return: The incident data as a dictionary.
        """
        return self._http_request(method="GET", url_suffix=f"{GET_RISK_FINDING_BY_ID}{risk_id}")

    def update_risk_status(self, risk_id: str, updated_status: str):
        return self._http_request(
            method="PATCH",
            url_suffix=f"/v1/risk-findings/id/{risk_id}/status/{updated_status}",
        )

    def get_alerts_list(self, params: dict[str, Any]):
        return self._http_request(method="GET", url_suffix=f"{GET_ALERTS_LIST}", params=params)

    def update_alert_status(self, alert_id: str, updated_status: str):
        return self._http_request(
            method="PATCH",
            url_suffix=f"/v1/alerts/id/{alert_id}/status/{updated_status}",
        )

    def get_list_of_asset_fields(self, params):
        return self._http_request(method="POST", url_suffix=f"{GET_ASSET_FIELDS}", params=params)


""" HELPER FUNCTIONS """


def validate_parameter(param_name: str, param_in: str, param_equal: str, supported_list: list[str]):
    if param_in:
        param_list = [item.strip() for item in param_in.split(",") if item.strip()]
        for param in param_list:
            if param not in supported_list:
                raise ValueError(f'This "{param}" {param_name} is not supported')

    if param_equal and param_equal not in supported_list:
        raise ValueError(f'This "{param_equal}" {param_name} is not supported')


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication"""
    try:
        # validate dspm creds
        client.get_data_types()
        return "ok"

    except DemistoException as e:
        if "Forbidden" in str(e) or "Authorization" in str(e):
            return "Authorization Error: make sure DSPM API Key is correctly set"
        else:
            return f"Error: An unknown exception occurred: {e}"


def get_list_risk_findings(client: Client, args: dict[str, Any], page: int) -> list[dict]:
    """Fetch list of DSPM Risk findings"""
    # Validate and process cloudProvider parameters
    cloud_provider_in = args.get("cloud_provider_in", "")
    cloud_provider_equal = args.get("cloud_provider_equal", "")
    validate_parameter("cloudProvider", cloud_provider_in, cloud_provider_equal, SUPPORTED_CLOUD_PROVIDERS)

    # Check supported affects
    affects_in = args.get("affects_in", "")
    affects_equal = args.get("affects_equal", "")
    validate_parameter("affects", affects_in, affects_equal, SUPPORTED_CATEGORIES)

    # Check supported Status
    status_in = args.get("status_in", "")
    status_equal = args.get("status_equal", "")
    validate_parameter("status", status_in, status_equal, SUPPORTED_STATUSES)

    # Check supported sorting order
    sort_order = args.get("sort")
    if sort_order:
        pattern = r"^.*,(ASC|DESC)$"
        matches = re.findall(pattern, sort_order, re.IGNORECASE)  # type: ignore
        if not matches:
            raise ValueError(f'This "{sort_order}" sorting order is not supported')

    params = {
        "ruleName.in": args.get("rule_name_in"),
        "ruleName.equals": args.get("rule_name_equal"),
        "dspmTagKey.in": args.get("dspm_tag_key_in"),
        "dspmTagKey.equals": args.get("dspm_tag_key_equal"),
        "dspmTagValue.in": args.get("dspm_tag_value_in"),
        "dspmTagValue.equals": args.get("dspm_tag_value_equal"),
        "projectId.in": args.get("projectId_in"),
        "projectId.equals": args.get("projectId_equal"),
        "cloudProvider.in": args.get("cloud_provider_in"),
        "cloudProvider.equals": args.get("cloud_provider_equal"),
        "affects.in": args.get("affects_in"),
        "affects.equals": args.get("affects_equal"),
        "status.in": args.get("status_in"),
        "status.equals": args.get("status_equal"),
        "sort": args.get("sort"),
        "page": page,
        "size": MAX_PAGE_SIZE,
    }
    # Remove None values from params
    params = {k: v for k, v in params.items() if v is not None}
    demisto.debug(f"params : {params}")

    # Fetch data from client
    findings = client.fetch_risk_findings(params)

    if not findings:
        return []  # No more findings to fetch

    return findings


def get_risk_finding_by_id(client: Client, args: dict[str, Any]) -> CommandResults:
    risk_id = args.get("finding_id")
    if not risk_id:
        raise ValueError("finding_id argument is required")

    # Fetch data from client using the get_risk_information method
    response = client.get_risk_information(risk_id)

    if not response:
        raise ValueError(f"No risk found with id {risk_id}")

    finding = response if isinstance(response, dict) else response[0]

    readable_output = tableToMarkdown("Risk Finding", finding, headerTransform=pascalToSpace)

    return CommandResults(
        outputs_prefix="DSPM.RiskFinding",
        outputs_key_field="id",
        outputs=finding,
        readable_output=readable_output,
        raw_response=finding,
    )


def get_list_of_assets(client: Client, args: dict[str, Any], page: int) -> list[dict]:
    # Validate and process cloudProvider parameters
    cloud_provider_in = args.get("cloud_provider_in", "")
    cloud_provider_equal = args.get("cloud_provider_equal", "")
    validate_parameter("cloudProvider", cloud_provider_in, cloud_provider_equal, SUPPORTED_CLOUD_PROVIDERS)

    # Validate and process serviceType parameters
    service_Type_In = args.get("service_type_in", "")
    service_Type_Equal = args.get("service_type_equal", "")
    validate_parameter("serviceType", service_Type_In, service_Type_Equal, SUPPORTED_SERVICE_TYPES)

    # Validate and process lifecycle parameters
    lifecycle_In = args.get("lifecycle_in", "")
    lifecycle_Equal = args.get("lifecycle_equal", "")
    validate_parameter("lifecycle", lifecycle_In, lifecycle_Equal, SUPPORTED_LIFECYCLE)

    # Check supported sorting order
    sort_order = args.get("sort")
    if sort_order:
        pattern = r"^.*,(ASC|DESC)$"
        matches = re.findall(pattern, sort_order, re.IGNORECASE)  # type: ignore
        if not matches:
            raise ValueError(f'This "{sort_order}" sorting order is not supported')

    params = {
        "region.in": args.get("region_in"),
        "region.equals": args.get("region_equal"),
        "cloudProvider.in": args.get("cloud_provider_in"),
        "cloudProvider.equals": args.get("cloud_provider_equal"),
        "serviceType.in": args.get("service_type_in"),
        "serviceType.equals": args.get("service_type_equal"),
        "digTagKey.contains": args.get("dig_tag_key_contains"),
        "digTagValue.contains": args.get("dig_tag_value_contains"),
        "lifecycle.in": args.get("lifecycle_in"),
        "lifecycle.equals": args.get("lifecycle_equal"),
        "sort": args.get("sort"),
        "page": page,
        "size": MAX_PAGE_SIZE,
    }
    # Remove None values from params
    params = {k: v for k, v in params.items() if v is not None}

    # Fetch data from client
    response = client.get_asset_lists(params)

    assets = response.get("assets", []) if isinstance(response, dict) else response

    if not assets:
        return []  # No more assets to fetch

    return assets


def get_asset_details(client: Client, args: dict[str, Any]) -> CommandResults:
    asset_id = args.get("asset_id", None)
    if not asset_id:
        raise ValueError("asset_id not specified")

    asset_details = client.get_asset_details(asset_id)

    readable_output = tableToMarkdown("Asset Details", asset_details, headerTransform=pascalToSpace)
    return CommandResults(
        outputs_prefix="DSPM.AssetDetails",
        outputs_key_field="id",
        outputs=asset_details,
        readable_output=readable_output,
        raw_response=asset_details,
    )


def get_asset_files_by_id(client: Client, args: dict[str, Any]) -> CommandResults:
    asset_id = args.get("asset_id", None)
    if not asset_id:
        raise ValueError("Asset ID not specified")

    page_number = 1
    all_files = []

    while True:
        params = {"id": asset_id, "page": page_number, "size": MAX_PAGE_SIZE}

        # Fetch the asset files for the current page
        response = client.get_asset_files(params)
        files = response.get("files", [])

        if not files:
            break  # Stop when there are no more files to retrieve

        # Append the files to the total list of files
        all_files.extend(files)

        # Increment page number for the next fetch
        page_number += 1

    files_count = len(all_files)

    # Return the result without formatting the files structure
    readable_output = tableToMarkdown("Asset Files", all_files, headerTransform=pascalToSpace)
    return CommandResults(
        outputs_prefix="DSPM.AssetFiles",
        outputs_key_field="filename",
        outputs={"files": all_files, "filesCount": files_count},
        readable_output=readable_output,
        raw_response=all_files,
    )


def get_data_types(client: Client) -> CommandResults:
    """Command to fetch data types."""
    data_types = client.get_data_types()
    data_types_formatted = [{"No": index + 1, "Key": dt} for index, dt in enumerate(data_types)]

    table_name = "Data Types"
    headers = ["No", "Key"]
    readable_output = tableToMarkdown(table_name, data_types_formatted, headers=headers)

    return CommandResults(
        outputs_prefix="DSPM.DataTypes",
        outputs_key_field="Key",
        outputs=data_types_formatted,
        readable_output=readable_output,
        raw_response=data_types,
    )


def get_data_type_findings(client: Client, args: dict[str, Any], page: int) -> list[dict]:
    """Fetch data type findings for a specific page."""
    # check supported cloud providers
    cloud_provider_in = args.get("cloud_provider_in", "")
    cloud_provider_equal = args.get("cloud_provider_equal", "")
    validate_parameter("cloudProvider", cloud_provider_in, cloud_provider_equal, SUPPORTED_CLOUD_PROVIDERS)

    # check supported service type
    service_type_in = args.get("service_type_in", "")
    service_type_equal = args.get("service_type_equal", "")
    validate_parameter("serviceType", service_type_in, service_type_equal, SUPPORTED_SERVICE_TYPES)

    # check supported lifecycle
    lifecycle_in = args.get("lifecycle_in", "")
    lifecycle_equal = args.get("lifecycle_equal", "")
    validate_parameter("lifecycle", lifecycle_in, lifecycle_equal, SUPPORTED_LIFECYCLE)

    # Check supported sorting order
    sort_order = args.get("sort")
    if sort_order:
        pattern = r"^.*,(ASC|DESC)$"
        matches = re.findall(pattern, sort_order, re.IGNORECASE)  # type: ignore
        if not matches:
            raise ValueError(f'This "{sort_order}" sorting order is not supported')

    params = {
        "region.in": args.get("region_in"),
        "region.equals": args.get("region_equal"),
        "projectId.in": args.get("projectId_in"),
        "projectId.equals": args.get("projectId_equal"),
        "cloudProvider.in": args.get("cloud_provider_in"),
        "cloudProvider.equals": args.get("cloud_provider_equal"),
        "serviceType.in": args.get("service_type_in"),
        "serviceType.equals": args.get("service_type_equal"),
        "lifecycle.in": args.get("lifecycle_in"),
        "lifecycle.equals": args.get("lifecycle_equal"),
        "sort": args.get("sort"),
        "page": page,
        "size": MAX_PAGE_SIZE,
    }

    # Remove None values from params
    params = {k: v for k, v in params.items() if v is not None}

    data_type_findings = client.get_data_type_findings(params)
    return data_type_findings


def update_risk_finding_status(client, args):
    finding_id = args.get("risk_finding_id")
    status = args.get("status")
    if status and status not in SUPPORTED_STATUSES:
        raise ValueError(f'This "{status}" status is not supported')

    try:
        response = client.update_risk_status(finding_id, status)
        # Format the response for display
        markdown = tableToMarkdown("Risk Status Update", [response], headerTransform=pascalToSpace)

        return CommandResults(
            readable_output=markdown,
            outputs_prefix="DSPM.RiskFindingStatusUpdate",
            outputs_key_field="riskFindingId",
            outputs=response,
            raw_response=response,
        )
    except Exception as e:
        return_error(f"Failed to update risk finding {finding_id} to status {status}. Error: {str(e)}")


def get_list_of_alerts(client: Client, args: dict[str, Any], page: int) -> list[dict]:
    """fetch list of dspm alerts"""
    # check supported cloud providers
    cloud_provider_in = args.get("cloud_provider_in", "")
    cloud_provider_equal = args.get("cloud_provider_equal", "")
    validate_parameter("cloudProvider", cloud_provider_in, cloud_provider_equal, SUPPORTED_CLOUD_PROVIDERS)

    # check supported cloud environments
    cloud_environment_in = args.get("cloud_environment_in", "")
    cloud_environment_equal = args.get("cloud_environment_equal", "")
    validate_parameter("cloudEnvironment", cloud_environment_in, cloud_environment_equal, SUPPORTED_CLOUD_ENVIRONMENTS)

    # check supported policy severity
    policy_severity_in = args.get("policy_severity_in", "")
    policy_severity_equal = args.get("policy_severity_equal", "")
    validate_parameter("policySeverity", policy_severity_in, policy_severity_equal, SUPPORTED_POLICY_SEVERITIES)

    # check supported category type
    category_type_in = args.get("category_type_in", "")
    category_type_equal = args.get("category_type_equal", "")
    validate_parameter("categoryType", category_type_in, category_type_equal, SUPPORTED_CATEGORY_TYPES)

    # check supported category type
    status_in = args.get("status_in", "")
    status_equal = args.get("status_equal", "")
    validate_parameter("status", status_in, status_equal, SUPPORTED_STATUSES)

    # Check supported sorting order
    sort_order = args.get("sort")
    if sort_order:
        pattern = r"^.*,(ASC|DESC)$"
        matches = re.findall(pattern, sort_order, re.IGNORECASE)  # type: ignore
        if not matches:
            raise ValueError(f'This "{sort_order}" sorting order is not supported')

    params = {
        "detectionTime.equals": args.get("detection_time_equals"),
        "detectionTime.greaterThanOrEqual": args.get("detection_time_greater_than_or_equal"),
        "detectionTime.greaterThan": args.get("detection_time_greater_than"),
        "detectionTime.lessThanOrEqual": args.get("detection_time_less_than_or_equal"),
        "detectionTime.lessThan": args.get("detection_time_less_than"),
        "policyName.in": args.get("policy_name_in"),
        "policyName.equals": args.get("policy_name_equals"),
        "assetName.in": args.get("asset_name_in"),
        "assetName.equals": args.get("asset_name_equals"),
        "cloudProvider.in": args.get("cloud_provider_in"),
        "cloudProvider.equals": args.get("cloud_provider_equals"),
        "destinationProjectVendorName.in": args.get("destination_project_vendor_name_in"),
        "destinationProjectVendorName.equals": args.get("destination_project_vendor_name_equals"),
        "cloudEnvironment.in": args.get("cloud_environment_in"),
        "cloudEnvironment.equals": args.get("cloud_environment_equals"),
        "policySeverity.in": args.get("policy_severity_in"),
        "policySeverity.equals": args.get("policy_severity_equals"),
        "categoryType.in": args.get("category_type_in"),
        "categoryType.equals": args.get("category_type_equals"),
        "status.in": args.get("status_in"),
        "status.equals": args.get("status_equals"),
        "sort": args.get("sort"),
        "page": page,
        "size": MAX_PAGE_SIZE,
    }

    # Remove None values from params
    params = {k: v for k, v in params.items() if v is not None}

    alerts_list = client.get_alerts_list(params)
    return alerts_list


def update_dspm_alert_status(client, args):
    alert_id = args.get("alert_id")
    status = args.get("status")
    if status and status not in SUPPORTED_STATUSES:
        raise ValueError(f'This "{status}" status is not supported')

    try:
        response = client.update_alert_status(alert_id, status)
        # Format the response for display

        markdown = tableToMarkdown("Alert Status Update", [response], headerTransform=pascalToSpace)

        return CommandResults(
            readable_output=markdown,
            outputs_prefix="DSPM.AlertStatusUpdate",
            outputs_key_field="alertId",
            outputs=response,
            raw_response=response,
        )
    except Exception as e:
        return_error(f"Failed to update alert '{alert_id}' to status '{status}'. Error: {str(e)}")


def get_list_of_labels(client: Client):
    """Command to fetch list of label names based on company"""
    labels = client.get_labels()
    labels_formatted = [{"No": index + 1, "Key": dt} for index, dt in enumerate(labels)]

    table_name = "Labels"
    headers = ["No", "Key"]
    readable_output = tableToMarkdown(table_name, labels_formatted, headers=headers)

    return CommandResults(
        outputs_prefix="DSPM.Label",
        outputs_key_field="Key",
        outputs=labels_formatted,
        readable_output=readable_output,
        raw_response=labels,
    )


def dspm_list_risk_findings_command(client, args):
    limit = args.get("limit", DEFAULT_LIMIT)
    if not limit.isdigit():
        raise ValueError("The 'limit' parameter must be an integer.")
    limit = int(limit)
    page = 0
    findings_collected: list = []

    while len(findings_collected) < limit:
        findings = get_list_risk_findings(client, args, page)
        if not findings:
            if page == 0 and not findings_collected:
                demisto.info("No risks were fetched")

                return CommandResults(readable_output="No Risk Findings found.")

            break  # No more findings to fetch

        findings_collected.extend(findings)
        if len(findings_collected) >= limit:
            break

        page += 1

    # Trim findings to match the limit
    findings_collected = findings_collected[:limit]

    # Prepare the readable output
    readable_output = tableToMarkdown("Risk Findings", findings_collected, headerTransform=pascalToSpace)

    # Return a single CommandResults with all findings
    return CommandResults(
        outputs_prefix="DSPM.RiskFinding",
        outputs_key_field="id",
        outputs=findings_collected,
        readable_output=readable_output,
        raw_response=findings_collected,
    )


def dspm_list_assets_command(client, args):
    limit = args.get("limit", DEFAULT_LIMIT)
    if not limit.isdigit():
        raise ValueError("The 'limit' parameter must be an integer.")
    limit = int(limit)
    page = 0
    collected_assets: list = []

    while len(collected_assets) < limit:
        assets = get_list_of_assets(client, args, page)
        if not assets:
            if page == 0 and not collected_assets:
                return CommandResults(readable_output="No assets found.")

            break

        collected_assets.extend(assets)
        if len(collected_assets) >= limit:
            break

        page += 1

    # Trim the results to match the limit
    collected_assets = collected_assets[:limit]

    # Generate readable output
    readable_output = tableToMarkdown("List of Assets", collected_assets, headerTransform=pascalToSpace)

    # Return the collected assets directly without parsing
    return CommandResults(
        outputs_prefix="DSPM.Asset",
        outputs_key_field="id",
        outputs=collected_assets,
        readable_output=readable_output,
        raw_response=collected_assets,
    )


def dspm_list_data_types_findings_command(client, args):
    limit = args.get("limit", DEFAULT_LIMIT)
    if not limit.isdigit():
        raise ValueError("The 'limit' parameter must be an integer.")
    limit = int(limit)
    page = 0
    collected_data_types: list = []

    while len(collected_data_types) < limit:
        data_type_findings = get_data_type_findings(client, args, page)
        if not data_type_findings:
            if page == 0 and not collected_data_types:
                return CommandResults(
                    readable_output="No Data Types findings found.",
                )

            break

        collected_data_types.extend(data_type_findings)
        if len(collected_data_types) >= limit:
            break

        page += 1

    # Trim the results to match the limit
    collected_data_types = collected_data_types[:limit]

    readable_output = tableToMarkdown("Data Types Finding", collected_data_types, headerTransform=pascalToSpace)
    return CommandResults(
        outputs_prefix="DSPM.DataTypesFinding",
        outputs_key_field="dataTypeName",
        outputs=collected_data_types,
        readable_output=readable_output,
        raw_response=collected_data_types,
    )


def dspm_list_alerts_command(client, args):
    limit = args.get("limit", DEFAULT_LIMIT)
    if not limit.isdigit():
        raise ValueError("The 'limit' parameter must be an integer.")
    limit = int(limit)
    page = 0
    collected_alerts: list = []

    while len(collected_alerts) < limit:
        alerts = get_list_of_alerts(client, args, page)
        if not alerts:
            if page == 0 and not collected_alerts:
                return CommandResults(
                    readable_output="No alerts found.",
                )

            break

        collected_alerts.extend(alerts)
        if len(collected_alerts) >= limit:
            break
        page += 1

    # Trim the results to match the limit
    collected_alerts = collected_alerts[:limit]

    readable_output = tableToMarkdown("List Of Alerts", collected_alerts, headerTransform=pascalToSpace)
    return CommandResults(
        outputs_prefix="DSPM.Alert",
        outputs_key_field="id",
        outputs=collected_alerts,
        readable_output=readable_output,
        raw_response=collected_alerts,
    )


def dspm_get_list_of_asset_fields_command(client: Client, args: dict[str, Any]) -> CommandResults:
    asset_id = args.get("assetId", None)
    if not asset_id:
        raise ValueError("Asset ID not specified")

    page_number = 1
    all_fields = []
    try:
        while True:
            params = {"id": asset_id, "page": page_number, "size": MAX_PAGE_SIZE}

            # Fetch the asset fields for the current page
            response = client.get_list_of_asset_fields(params)
            if isinstance(response, list) and len(response) > 0:
                fields = response[0].get("fields", [])
            else:
                fields = response.get("fields", [])
            # fields = response[0].get("fields", [])

            if not fields:
                break

            # Append the fields to the total list of fields
            all_fields.extend(fields)

            # Increment page number for the next fetch
            page_number += 1

        fields_count = len(all_fields)

        # Return the result without formatting the fields structure
        headers = ["name", "dataTypes", "path", "tableName", "tableSize", "databaseName", "collectionName", "type", "schemaName"]
        readable_output = tableToMarkdown("Asset Field", all_fields, headers=headers, headerTransform=pascalToSpace)
        return CommandResults(
            outputs_prefix="DSPM.AssetFields",
            outputs_key_field="name",
            outputs={"fields": all_fields, "fieldsCount": fields_count},
            readable_output=readable_output,
        )
    except Exception as e:
        error = str(e)
        if '"status": 400' in error:
            message = f"Provided assetID:- '{asset_id}' does not supported. Please check the command description for more details"
        elif '"status": 404' in error:
            message = f"Incorrect assetID:- '{asset_id}' provided. Please confirm the assetID"
        else:
            message = f"Failed to get asset fields for assetId:- '{asset_id}'. Error: {str(e)}"
        raise Exception(message)


""" MAIN FUNCTION """


def main() -> None:
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    # get the service API url
    base_url = demisto.params().get("dspmBaseUrl")
    api_key = demisto.params().get("dspmApiKey", {}).get("password")
    verify_certificate = not demisto.params().get("insecure", False)
    proxy = demisto.params().get("proxy", False)

    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        client = Client(base_url=base_url, api_key=api_key, verify=verify_certificate, proxy=proxy)

        if demisto.command() == "test-module":
            result = test_module(client)
            return_results(result)

        #
        # labels-resource
        #
        elif demisto.command() == "dspm-list-labels":
            return_results(get_list_of_labels(client))

        #
        # risk-resource
        #
        elif demisto.command() == "dspm-list-risk-findings":
            return_results(dspm_list_risk_findings_command(client, demisto.args()))
        elif demisto.command() == "dspm-get-risk-finding-by-id":
            return_results(get_risk_finding_by_id(client, demisto.args()))
        elif demisto.command() == "dspm-update-risk-finding-status":
            return_results(update_risk_finding_status(client, demisto.args()))

        #
        # asset-resource
        #

        elif demisto.command() == "dspm-list-assets":
            return_results(dspm_list_assets_command(client, demisto.args()))
        elif demisto.command() == "dspm-get-asset-details":
            return_results(get_asset_details(client, demisto.args()))

        #
        # classification-resource
        #
        elif demisto.command() == "dspm-get-data-types":
            return_results(get_data_types(client))
        elif demisto.command() == "dspm-get-asset-files-by-id":
            return_results(get_asset_files_by_id(client, demisto.args()))
        elif demisto.command() == "dspm-get-list-of-asset-fields-by-id":
            return_results(dspm_get_list_of_asset_fields_command(client, demisto.args()))

        #
        # data-type-findings-resource
        #
        elif demisto.command() == "dspm-list-data-types-findings":
            return_results(dspm_list_data_types_findings_command(client, demisto.args()))

        #
        # alert-resource
        #
        elif demisto.command() == "dspm-update-alert-status":
            return_results(update_dspm_alert_status(client, demisto.args()))
        elif demisto.command() == "dspm-list-alerts":
            return_results(dspm_list_alerts_command(client, demisto.args()))

    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{str(e)}")


""" ENTRY POINT """
if __name__ in ("__main__", "__builtin__", "builtins"):
    main()