From a65122b4555d9f4a29723f5a7852a548ad1167e4 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 22:08:23 -0700 Subject: [PATCH 1/8] feat(integrations): add Vanta integration with compliance, evidence file, people, vendor, vulnerability, and risk tools --- apps/docs/components/icons.tsx | 48 + apps/docs/components/ui/icon-mapping.ts | 2 + .../content/docs/en/integrations/meta.json | 1 + .../content/docs/en/integrations/vanta.mdx | 690 ++++++++++ .../sim/app/api/tools/vanta/download/route.ts | 128 ++ apps/sim/app/api/tools/vanta/query/route.ts | 420 ++++++ apps/sim/app/api/tools/vanta/upload/route.ts | 139 ++ apps/sim/blocks/blocks/vanta.ts | 1225 +++++++++++++++++ apps/sim/blocks/registry.ts | 3 + apps/sim/components/icons.tsx | 48 + apps/sim/lib/api/contracts/tools/index.ts | 1 + apps/sim/lib/api/contracts/tools/vanta.ts | 732 ++++++++++ apps/sim/lib/integrations/icon-mapping.ts | 2 + apps/sim/lib/integrations/integrations.json | 135 ++ apps/sim/tools/registry.ts | 60 + .../sim/tools/vanta/download_document_file.ts | 74 + apps/sim/tools/vanta/get_control.ts | 64 + apps/sim/tools/vanta/get_document.ts | 64 + apps/sim/tools/vanta/get_framework.ts | 65 + apps/sim/tools/vanta/get_person.ts | 64 + apps/sim/tools/vanta/get_policy.ts | 64 + apps/sim/tools/vanta/get_risk_scenario.ts | 67 + apps/sim/tools/vanta/get_test.ts | 62 + apps/sim/tools/vanta/get_vendor.ts | 64 + apps/sim/tools/vanta/get_vulnerable_asset.ts | 70 + apps/sim/tools/vanta/index.ts | 30 + .../sim/tools/vanta/list_control_documents.ts | 94 ++ apps/sim/tools/vanta/list_control_tests.ts | 91 ++ apps/sim/tools/vanta/list_controls.ts | 89 ++ apps/sim/tools/vanta/list_document_uploads.ts | 94 ++ apps/sim/tools/vanta/list_documents.ts | 100 ++ .../tools/vanta/list_framework_controls.ts | 94 ++ apps/sim/tools/vanta/list_frameworks.ts | 85 ++ .../tools/vanta/list_monitored_computers.ts | 96 ++ apps/sim/tools/vanta/list_people.ts | 126 ++ apps/sim/tools/vanta/list_policies.ts | 83 ++ apps/sim/tools/vanta/list_risk_scenarios.ts | 169 +++ apps/sim/tools/vanta/list_test_entities.ts | 102 ++ apps/sim/tools/vanta/list_tests.ts | 133 ++ apps/sim/tools/vanta/list_vendors.ts | 97 ++ apps/sim/tools/vanta/list_vulnerabilities.ts | 165 +++ .../vanta/list_vulnerability_remediations.ts | 123 ++ .../sim/tools/vanta/list_vulnerable_assets.ts | 117 ++ apps/sim/tools/vanta/outputs.ts | 794 +++++++++++ apps/sim/tools/vanta/submit_document.ts | 63 + apps/sim/tools/vanta/types.ts | 779 +++++++++++ apps/sim/tools/vanta/upload_document_file.ts | 104 ++ apps/sim/tools/vanta/utils.ts | 698 ++++++++++ scripts/check-api-validation-contracts.ts | 4 +- 49 files changed, 8620 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/docs/en/integrations/vanta.mdx create mode 100644 apps/sim/app/api/tools/vanta/download/route.ts create mode 100644 apps/sim/app/api/tools/vanta/query/route.ts create mode 100644 apps/sim/app/api/tools/vanta/upload/route.ts create mode 100644 apps/sim/blocks/blocks/vanta.ts create mode 100644 apps/sim/lib/api/contracts/tools/vanta.ts create mode 100644 apps/sim/tools/vanta/download_document_file.ts create mode 100644 apps/sim/tools/vanta/get_control.ts create mode 100644 apps/sim/tools/vanta/get_document.ts create mode 100644 apps/sim/tools/vanta/get_framework.ts create mode 100644 apps/sim/tools/vanta/get_person.ts create mode 100644 apps/sim/tools/vanta/get_policy.ts create mode 100644 apps/sim/tools/vanta/get_risk_scenario.ts create mode 100644 apps/sim/tools/vanta/get_test.ts create mode 100644 apps/sim/tools/vanta/get_vendor.ts create mode 100644 apps/sim/tools/vanta/get_vulnerable_asset.ts create mode 100644 apps/sim/tools/vanta/index.ts create mode 100644 apps/sim/tools/vanta/list_control_documents.ts create mode 100644 apps/sim/tools/vanta/list_control_tests.ts create mode 100644 apps/sim/tools/vanta/list_controls.ts create mode 100644 apps/sim/tools/vanta/list_document_uploads.ts create mode 100644 apps/sim/tools/vanta/list_documents.ts create mode 100644 apps/sim/tools/vanta/list_framework_controls.ts create mode 100644 apps/sim/tools/vanta/list_frameworks.ts create mode 100644 apps/sim/tools/vanta/list_monitored_computers.ts create mode 100644 apps/sim/tools/vanta/list_people.ts create mode 100644 apps/sim/tools/vanta/list_policies.ts create mode 100644 apps/sim/tools/vanta/list_risk_scenarios.ts create mode 100644 apps/sim/tools/vanta/list_test_entities.ts create mode 100644 apps/sim/tools/vanta/list_tests.ts create mode 100644 apps/sim/tools/vanta/list_vendors.ts create mode 100644 apps/sim/tools/vanta/list_vulnerabilities.ts create mode 100644 apps/sim/tools/vanta/list_vulnerability_remediations.ts create mode 100644 apps/sim/tools/vanta/list_vulnerable_assets.ts create mode 100644 apps/sim/tools/vanta/outputs.ts create mode 100644 apps/sim/tools/vanta/submit_document.ts create mode 100644 apps/sim/tools/vanta/types.ts create mode 100644 apps/sim/tools/vanta/upload_document_file.ts create mode 100644 apps/sim/tools/vanta/utils.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 1368eb93b3..573eb40256 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -7555,6 +7555,54 @@ export function OnePasswordIcon(props: SVGProps) { ) } +export function VantaIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + + ) +} + export function VercelIcon(props: SVGProps) { return ( = { twilio_voice: TwilioIcon, typeform: TypeformIcon, upstash: UpstashIcon, + vanta: VantaIcon, vercel: VercelIcon, video_generator: VideoIcon, video_generator_v2: VideoIcon, diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index 0ee13b9b63..b40e2f111e 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -208,6 +208,7 @@ "twilio_voice", "typeform", "upstash", + "vanta", "vercel", "wealthbox", "webflow", diff --git a/apps/docs/content/docs/en/integrations/vanta.mdx b/apps/docs/content/docs/en/integrations/vanta.mdx new file mode 100644 index 0000000000..154dc7dfd4 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/vanta.mdx @@ -0,0 +1,690 @@ +--- +title: Vanta +description: Query compliance status and manage evidence in Vanta +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Vanta](https://www.vanta.com/) is a trust management platform that automates security and compliance for frameworks like SOC 2, ISO 27001, HIPAA, and GDPR. It continuously monitors your infrastructure, people, and vendors through automated tests, and centralizes the evidence auditors need. + +With the Vanta integration in Sim, you can: + +- **Monitor compliance posture**: List frameworks with control, document, and test completion counts, and drill into individual controls and their mapped tests and evidence documents. +- **Triage failing tests**: List automated compliance tests by status, framework, integration, or category, and pull the exact failing resource entities that need remediation. +- **Manage evidence documents**: List and inspect evidence documents, upload evidence files with descriptions and effective dates, download previously uploaded files, and submit document collections for auditor review. +- **Track people and security tasks**: List people with employment status, group membership, and outstanding security tasks (trainings, policy acceptance, background checks, device monitoring). +- **Review policies and vendors**: Check policy approval status and versions, and track vendors with risk levels, contract dates, and security review schedules. +- **Stay on top of vulnerabilities**: List vulnerabilities with severity and SLA deadline filters, review remediation history, and inspect the vulnerable assets behind each finding. +- **Watch device compliance**: List monitored computers with screenlock, disk encryption, password manager, and antivirus check outcomes. +- **Manage risk scenarios**: Query risk register scenarios with likelihood/impact scores, treatment decisions, and review status. + +The integration authenticates with Vanta OAuth client credentials (created under Settings → Developer Console in Vanta) and supports both the commercial (api.vanta.com) and FedRAMP (api.vanta-gov.com) environments. Evidence uploads require credentials granted the `vanta-api.documents:upload` scope. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate Vanta into the workflow. Monitor compliance frameworks, controls, and automated tests; find failing test entities; manage evidence documents including file upload, download, and submission; and track people, policies, vendors, monitored computers, vulnerabilities, and risk scenarios. Requires Vanta OAuth client credentials. + + + +## Actions + +### `vanta_list_frameworks` + +List the compliance frameworks (e.g., SOC 2, ISO 27001) available in a Vanta account with completion counts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `frameworks` | array | Frameworks in the Vanta account | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_framework` + +Get a Vanta compliance framework by ID, including its requirement categories and mapped controls + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `frameworkId` | string | Yes | Unique ID of the framework \(e.g., soc2\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `framework` | json | The requested framework with requirement categories | + +### `vanta_list_framework_controls` + +List the controls that belong to a specific Vanta compliance framework + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `frameworkId` | string | Yes | Unique ID of the framework \(e.g., soc2\) | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `controls` | array | Controls belonging to the framework | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_controls` + +List the security controls in a Vanta account, optionally filtered by framework + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `frameworkMatchesAny` | string | No | Comma-separated framework IDs to filter controls by \(e.g., soc2,iso27001\) | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `controls` | array | Controls matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_control` + +Get a Vanta security control by ID, including its status and evidence pass/fail counts + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `controlId` | string | Yes | Unique ID of the control | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `control` | json | The requested control with status and evidence counts | + +### `vanta_list_control_tests` + +List the automated tests mapped to a specific Vanta control + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `controlId` | string | Yes | Unique ID of the control | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tests` | array | Tests mapped to the control | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_control_documents` + +List the evidence documents mapped to a specific Vanta control + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `controlId` | string | Yes | Unique ID of the control | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `documents` | array | Documents mapped to the control | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_tests` + +List the automated compliance tests in a Vanta account, with filters for status, framework, integration, control, owner, and category + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `statusFilter` | string | No | Filter by test status: OK, DEACTIVATED, NEEDS_ATTENTION, IN_PROGRESS, INVALID, or NOT_APPLICABLE | +| `frameworkFilter` | string | No | Filter by framework ID \(e.g., soc2\) | +| `integrationFilter` | string | No | Filter by integration ID \(e.g., aws\) | +| `controlFilter` | string | No | Filter by control ID | +| `ownerFilter` | string | No | Filter by owner user ID | +| `categoryFilter` | string | No | Filter by test category \(e.g., ACCOUNTS_ACCESS, COMPUTERS, INFRASTRUCTURE, POLICIES, VULNERABILITY_MANAGEMENT\) | +| `isInRollout` | boolean | No | Filter by whether the test is in rollout | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tests` | array | Tests matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_test` + +Get a Vanta automated compliance test by ID, including its status and remediation info + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `testId` | string | Yes | Unique ID of the test \(e.g., test-aws-cloudtrail-enabled\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `test` | json | The requested test | + +### `vanta_list_test_entities` + +List the failing or deactivated resource entities for a specific Vanta test, useful for finding exactly which resources need remediation + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `testId` | string | Yes | Unique ID of the test \(e.g., test-aws-cloudtrail-enabled\) | +| `entityStatus` | string | No | Filter entities by status: FAILING or DEACTIVATED | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `entities` | array | Resource entities for the test | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_documents` + +List the evidence documents in a Vanta account, optionally filtered by framework or document status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `frameworkMatchesAny` | string | No | Comma-separated framework IDs to filter documents by \(e.g., soc2,iso27001\) | +| `statusMatchesAny` | string | No | Comma-separated document statuses to filter by: "Needs document", "Needs update", "Not relevant", "OK" | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `documents` | array | Documents matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_document` + +Get a Vanta evidence document by ID, including its renewal schedule and deactivation status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `documentId` | string | Yes | Unique ID of the document | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `document` | json | The requested document | + +### `vanta_list_document_uploads` + +List the files uploaded to a specific Vanta evidence document + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `documentId` | string | Yes | Unique ID of the document | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uploads` | array | Files uploaded to the document | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_upload_document_file` + +Upload an evidence file to a Vanta document. Requires credentials with the vanta-api.documents:upload scope. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `documentId` | string | Yes | Unique ID of the document to attach the file to | +| `file` | file | No | The evidence file to upload | +| `fileContent` | string | No | Base64-encoded file content \(alternative to file\) | +| `fileName` | string | No | Optional file name override | +| `description` | string | No | Description of the uploaded evidence \(e.g., "Q3 access review evidence"\) | +| `effectiveAtDate` | string | No | ISO 8601 date indicating when the document is effective from | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `upload` | json | Metadata of the uploaded file | + +### `vanta_download_document_file` + +Download a file previously uploaded to a Vanta evidence document and store it in execution files + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `documentId` | string | Yes | Unique ID of the document | +| `uploadedFileId` | string | Yes | Unique ID of the uploaded file \(from List Document Uploads\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `file` | file | Downloaded file stored in execution files | +| `name` | string | Name of the downloaded file | +| `mimeType` | string | MIME type of the downloaded file | +| `size` | number | Size of the downloaded file in bytes | + +### `vanta_submit_document` + +Submit a Vanta document collection for review so uploaded evidence becomes visible to auditors. Requires credentials with write access. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `documentId` | string | Yes | Unique ID of the document to submit | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `documentId` | string | ID of the submitted document | +| `submitted` | boolean | Whether the document collection was submitted | + +### `vanta_list_people` + +List the people tracked in a Vanta account with employment status, group membership, and security task completion + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `emailAndNameFilter` | string | No | Filter people by email address or name | +| `employmentStatus` | string | No | Filter by employment status: UPCOMING, CURRENT, ON_LEAVE, INACTIVE, or FORMER | +| `groupIdsMatchesAny` | string | No | Comma-separated group IDs to filter people by | +| `tasksSummaryStatusMatchesAny` | string | No | Comma-separated task summary statuses to filter by: NONE, DUE_SOON, OVERDUE, COMPLETE, PAUSED, OFFBOARDING_DUE_SOON, OFFBOARDING_OVERDUE, OFFBOARDING_COMPLETE | +| `taskTypeMatchesAny` | string | No | Comma-separated task types to filter by: COMPLETE_TRAININGS, ACCEPT_POLICIES, COMPLETE_CUSTOM_TASKS, COMPLETE_CUSTOM_OFFBOARDING_TASKS, INSTALL_DEVICE_MONITORING, COMPLETE_BACKGROUND_CHECKS | +| `taskStatusMatchesAny` | string | No | Comma-separated task statuses to filter by: COMPLETE, DUE_SOON, OVERDUE, NONE | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `people` | array | People matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_person` + +Get a person tracked in Vanta by ID, including employment, leave, and security task status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `personId` | string | Yes | Unique ID of the person | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `person` | json | The requested person | + +### `vanta_list_policies` + +List the security policies in a Vanta account with approval status and version info + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `policies` | array | Policies in the Vanta account | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_policy` + +Get a Vanta security policy by ID, including its approval status and latest approved version documents + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `policyId` | string | Yes | Unique ID of the policy | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `policy` | json | The requested policy | + +### `vanta_list_vendors` + +List the vendors tracked in a Vanta account with risk levels, contract dates, and security review schedules + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `name` | string | No | Filter vendors by name | +| `statusMatchesAny` | string | No | Comma-separated vendor statuses to filter by: MANAGED, ARCHIVED, IN_PROCUREMENT | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `vendors` | array | Vendors matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_vendor` + +Get a Vanta vendor by ID, including risk levels, contract details, and authentication info + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `vendorId` | string | Yes | Unique ID of the vendor | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `vendor` | json | The requested vendor | + +### `vanta_list_monitored_computers` + +List the monitored computers in a Vanta account with screenlock, disk encryption, password manager, and antivirus check outcomes + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `complianceStatusFilterMatchesAny` | string | No | Comma-separated compliance issues to filter by: PWM_NOT_INSTALLED, HD_NOT_ENCRYPTED, AV_NOT_INSTALLED, SCREENLOCK_NOT_CONFIGURED, LAST_CHECK_OVER_14_DAYS | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `computers` | array | Monitored computers matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_vulnerabilities` + +List the vulnerabilities detected across a Vanta account with filters for severity, fixability, SLA deadlines, package, and integration + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `q` | string | No | Search query for vulnerabilities | +| `severity` | string | No | Filter by severity: LOW, MEDIUM, HIGH, or CRITICAL | +| `isFixAvailable` | boolean | No | Filter by whether a fix is available | +| `isDeactivated` | boolean | No | Filter by whether vulnerability monitoring is deactivated | +| `includeVulnerabilitiesWithoutSlas` | boolean | No | Include vulnerabilities that have no SLA deadline | +| `packageIdentifier` | string | No | Filter by the affected package identifier | +| `externalVulnerabilityId` | string | No | Filter by external vulnerability ID \(e.g., a CVE identifier\) | +| `integrationId` | string | No | Filter by the integration that detected the vulnerability | +| `vulnerableAssetId` | string | No | Filter by the vulnerable asset ID | +| `slaDeadlineAfterDate` | string | No | Only include vulnerabilities with an SLA deadline after this ISO 8601 date | +| `slaDeadlineBeforeDate` | string | No | Only include vulnerabilities with an SLA deadline before this ISO 8601 date | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `vulnerabilities` | array | Vulnerabilities matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_vulnerability_remediations` + +List remediated vulnerabilities in a Vanta account with detection, SLA deadline, and remediation dates + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `integrationId` | string | No | Filter by the integration that detected the vulnerability | +| `severity` | string | No | Filter by severity: LOW, MEDIUM, HIGH, or CRITICAL | +| `isRemediatedOnTime` | boolean | No | Filter by whether the vulnerability was remediated before its SLA deadline | +| `remediatedAfterDate` | string | No | Only include remediations completed after this ISO 8601 date | +| `remediatedBeforeDate` | string | No | Only include remediations completed before this ISO 8601 date | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `remediations` | array | Vulnerability remediations matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_list_vulnerable_assets` + +List the assets associated with vulnerabilities in a Vanta account (servers, repositories, workstations, and more) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `q` | string | No | Search query for vulnerable assets | +| `integrationId` | string | No | Filter by the integration scanning the asset | +| `assetType` | string | No | Filter by asset type: SERVER, SERVERLESS_FUNCTION, CONTAINER, CONTAINER_REPOSITORY, CONTAINER_REPOSITORY_IMAGE, CODE_REPOSITORY, MANIFEST_FILE, WORKSTATION, or OTHER | +| `assetExternalAccountId` | string | No | Filter by the external account ID the asset belongs to | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `assets` | array | Vulnerable assets matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_vulnerable_asset` + +Get a vulnerable asset in Vanta by ID, including the scanners reporting it and per-scanner asset details + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `vulnerableAssetId` | string | Yes | Unique ID of the vulnerable asset | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `asset` | json | The requested vulnerable asset | + +### `vanta_list_risk_scenarios` + +List the risk scenarios in a Vanta risk register with likelihood/impact scores, treatment decisions, and review status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `searchString` | string | No | Search string to filter risk scenarios | +| `includeIgnored` | boolean | No | Include ignored risk scenarios | +| `type` | string | No | Filter by scenario type: "Risk Scenario" or "Enterprise Risk" | +| `ownerMatchesAny` | string | No | Comma-separated owner emails to filter by | +| `categoryMatchesAny` | string | No | Comma-separated risk categories to filter by | +| `ciaCategoryMatchesAny` | string | No | Comma-separated CIA categories to filter by: Confidentiality, Integrity, Availability | +| `treatmentTypeMatchesAny` | string | No | Comma-separated treatments to filter by: Mitigate, Transfer, Avoid, Accept | +| `inherentScoreGroupMatchesAny` | string | No | Comma-separated inherent score groups to filter by: "Very low", Low, Med, High, Critical | +| `residualScoreGroupMatchesAny` | string | No | Comma-separated residual score groups to filter by: "Very low", Low, Med, High, Critical | +| `reviewStatusMatchesAny` | string | No | Comma-separated review statuses to filter by: APPROVED, DRAFT, NOT_REVIEWED, AWAITING_SUBMISSION, PENDING_APPROVAL, REQUESTED_CHANGES | +| `orderBy` | string | No | Field to order results by: description or createdAt | +| `pageSize` | number | No | Maximum number of items per page \(1-100, default 10\) | +| `pageCursor` | string | No | Pagination cursor: pass the endCursor from the previous response to fetch the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `riskScenarios` | array | Risk scenarios matching the filters | +| `pageInfo` | json | Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page | + +### `vanta_get_risk_scenario` + +Get a Vanta risk scenario by ID, including its scores, treatment decision, and review status + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `clientId` | string | Yes | Vanta OAuth application client ID | +| `clientSecret` | string | Yes | Vanta OAuth application client secret | +| `region` | string | No | Vanta API region: "us" \(api.vanta.com, default\) or "gov" \(api.vanta-gov.com\) | +| `riskScenarioId` | string | Yes | Unique ID of the risk scenario | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `riskScenario` | json | The requested risk scenario | + + diff --git a/apps/sim/app/api/tools/vanta/download/route.ts b/apps/sim/app/api/tools/vanta/download/route.ts new file mode 100644 index 0000000000..83198e2220 --- /dev/null +++ b/apps/sim/app/api/tools/vanta/download/route.ts @@ -0,0 +1,128 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { vantaDownloadContract } from '@/lib/api/contracts/tools/vanta' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + buildVantaUrl, + extractVantaError, + getVantaAccessToken, + getVantaBaseUrl, + VANTA_READ_SCOPE, +} from '@/tools/vanta/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('VantaDownloadAPI') + +const MAX_DOWNLOAD_SIZE_BYTES = 100 * 1024 * 1024 + +function downloadSizeError(bytes: number): NextResponse { + const sizeMB = (bytes / (1024 * 1024)).toFixed(2) + return NextResponse.json( + { success: false, error: `File size (${sizeMB}MB) exceeds download limit of 100MB` }, + { status: 400 } + ) +} + +/** + * Extracts the filename from a Content-Disposition header, if present. + */ +function getFileNameFromContentDisposition(header: string | null): string | null { + if (!header) return null + const utf8Match = header.match(/filename\*=UTF-8''([^;]+)/i) + if (utf8Match) { + try { + return decodeURIComponent(utf8Match[1]) + } catch { + return null + } + } + const plainMatch = header.match(/filename="?([^";]+)"?/i) + return plainMatch ? plainMatch[1] : null +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Vanta download attempt`, { + error: authResult.error || 'Unauthorized', + }) + return NextResponse.json( + { success: false, error: authResult.error || 'Unauthorized' }, + { status: 401 } + ) + } + + try { + const parsed = await parseRequest(vantaDownloadContract, request, {}) + if (!parsed.success) return parsed.response + const params = parsed.data.body + + const accessToken = await getVantaAccessToken({ + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope: VANTA_READ_SCOPE, + }) + + const mediaUrl = buildVantaUrl( + getVantaBaseUrl(params.region), + `/documents/${encodeURIComponent(params.documentId)}/uploads/${encodeURIComponent(params.uploadedFileId)}/media` + ) + + logger.info(`[${requestId}] Downloading Vanta document file`, { + documentId: params.documentId, + uploadedFileId: params.uploadedFileId, + }) + + const response = await fetch(mediaUrl, { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}` }, + cache: 'no-store', + }) + + if (!response.ok) { + const errorData: unknown = await response.json().catch(() => null) + const message = extractVantaError(errorData, 'Failed to download Vanta document file') + logger.error(`[${requestId}] Vanta download failed`, { status: response.status, message }) + return NextResponse.json({ success: false, error: message }, { status: response.status }) + } + + const contentLength = Number(response.headers.get('content-length')) + if (Number.isFinite(contentLength) && contentLength > MAX_DOWNLOAD_SIZE_BYTES) { + return downloadSizeError(contentLength) + } + + const buffer = Buffer.from(await response.arrayBuffer()) + if (buffer.length > MAX_DOWNLOAD_SIZE_BYTES) { + return downloadSizeError(buffer.length) + } + + const mimeType = response.headers.get('content-type') || 'application/octet-stream' + const name = + getFileNameFromContentDisposition(response.headers.get('content-disposition')) || + `vanta-document-file-${params.uploadedFileId}` + + logger.info(`[${requestId}] Vanta download successful`, { name, size: buffer.length }) + + return NextResponse.json({ + success: true, + output: { + file: { name, mimeType, data: buffer.toString('base64'), size: buffer.length }, + name, + mimeType, + size: buffer.length, + }, + }) + } catch (error) { + const message = toError(error).message + logger.error(`[${requestId}] Vanta download failed`, { error: message }) + return NextResponse.json({ success: false, error: message }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/vanta/query/route.ts b/apps/sim/app/api/tools/vanta/query/route.ts new file mode 100644 index 0000000000..6cd0bb7c58 --- /dev/null +++ b/apps/sim/app/api/tools/vanta/query/route.ts @@ -0,0 +1,420 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import type { VantaQueryBody } from '@/lib/api/contracts/tools/vanta' +import { vantaQueryContract } from '@/lib/api/contracts/tools/vanta' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + asVantaRecord, + buildVantaUrl, + extractVantaError, + getVantaAccessToken, + getVantaBaseUrl, + getVantaListResults, + normalizeVantaControl, + normalizeVantaControlDetail, + normalizeVantaDocument, + normalizeVantaDocumentDetail, + normalizeVantaFramework, + normalizeVantaFrameworkDetail, + normalizeVantaMonitoredComputer, + normalizeVantaPerson, + normalizeVantaPolicy, + normalizeVantaRiskScenario, + normalizeVantaTest, + normalizeVantaTestEntity, + normalizeVantaUploadedFile, + normalizeVantaVendor, + normalizeVantaVulnerability, + normalizeVantaVulnerabilityRemediation, + normalizeVantaVulnerableAsset, + splitVantaCommaList, + VANTA_DOCUMENT_UPLOAD_SCOPE, + VANTA_READ_SCOPE, +} from '@/tools/vanta/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('VantaQueryAPI') + +interface VantaApiRequest { + method: 'GET' | 'POST' + url: string +} + +/** + * Maps a validated query operation to the Vanta API request it performs. + */ +function buildVantaApiRequest(baseUrl: string, params: VantaQueryBody): VantaApiRequest { + const id = encodeURIComponent + + switch (params.operation) { + case 'vanta_list_frameworks': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/frameworks', { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_framework': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/frameworks/${id(params.frameworkId)}`) } + case 'vanta_list_framework_controls': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/frameworks/${id(params.frameworkId)}/controls`, { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_controls': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/controls', { + frameworkMatchesAny: splitVantaCommaList(params.frameworkMatchesAny), + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_control': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/controls/${id(params.controlId)}`) } + case 'vanta_list_control_tests': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/controls/${id(params.controlId)}/tests`, { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_control_documents': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/controls/${id(params.controlId)}/documents`, { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_tests': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/tests', { + statusFilter: params.statusFilter, + frameworkFilter: params.frameworkFilter, + integrationFilter: params.integrationFilter, + controlFilter: params.controlFilter, + ownerFilter: params.ownerFilter, + categoryFilter: params.categoryFilter, + isInRollout: params.isInRollout, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_test': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/tests/${id(params.testId)}`) } + case 'vanta_list_test_entities': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/tests/${id(params.testId)}/entities`, { + entityStatus: params.entityStatus, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_documents': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/documents', { + frameworkMatchesAny: splitVantaCommaList(params.frameworkMatchesAny), + statusMatchesAny: splitVantaCommaList(params.statusMatchesAny), + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_document': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/documents/${id(params.documentId)}`) } + case 'vanta_list_document_uploads': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/documents/${id(params.documentId)}/uploads`, { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_submit_document': + return { + method: 'POST', + url: buildVantaUrl(baseUrl, `/documents/${id(params.documentId)}/submit`), + } + case 'vanta_list_people': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/people', { + emailAndNameFilter: params.emailAndNameFilter, + employmentStatus: params.employmentStatus, + groupIdsMatchesAny: splitVantaCommaList(params.groupIdsMatchesAny), + tasksSummaryStatusMatchesAny: splitVantaCommaList(params.tasksSummaryStatusMatchesAny), + taskTypeMatchesAny: splitVantaCommaList(params.taskTypeMatchesAny), + taskStatusMatchesAny: splitVantaCommaList(params.taskStatusMatchesAny), + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_person': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/people/${id(params.personId)}`) } + case 'vanta_list_policies': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/policies', { + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_policy': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/policies/${id(params.policyId)}`) } + case 'vanta_list_vendors': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/vendors', { + name: params.name, + statusMatchesAny: splitVantaCommaList(params.statusMatchesAny), + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_vendor': + return { method: 'GET', url: buildVantaUrl(baseUrl, `/vendors/${id(params.vendorId)}`) } + case 'vanta_list_monitored_computers': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/monitored-computers', { + complianceStatusFilterMatchesAny: splitVantaCommaList( + params.complianceStatusFilterMatchesAny + ), + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_vulnerabilities': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/vulnerabilities', { + q: params.q, + severity: params.severity, + isFixAvailable: params.isFixAvailable, + isDeactivated: params.isDeactivated, + includeVulnerabilitiesWithoutSlas: params.includeVulnerabilitiesWithoutSlas, + packageIdentifier: params.packageIdentifier, + externalVulnerabilityId: params.externalVulnerabilityId, + integrationId: params.integrationId, + vulnerableAssetId: params.vulnerableAssetId, + slaDeadlineAfterDate: params.slaDeadlineAfterDate, + slaDeadlineBeforeDate: params.slaDeadlineBeforeDate, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_vulnerability_remediations': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/vulnerability-remediations', { + integrationId: params.integrationId, + severity: params.severity, + isRemediatedOnTime: params.isRemediatedOnTime, + remediatedAfterDate: params.remediatedAfterDate, + remediatedBeforeDate: params.remediatedBeforeDate, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_list_vulnerable_assets': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/vulnerable-assets', { + q: params.q, + integrationId: params.integrationId, + assetType: params.assetType, + assetExternalAccountId: params.assetExternalAccountId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_vulnerable_asset': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/vulnerable-assets/${id(params.vulnerableAssetId)}`), + } + case 'vanta_list_risk_scenarios': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, '/risk-scenarios', { + searchString: params.searchString, + includeIgnored: params.includeIgnored, + type: params.type, + ownerMatchesAny: splitVantaCommaList(params.ownerMatchesAny), + categoryMatchesAny: splitVantaCommaList(params.categoryMatchesAny), + ciaCategoryMatchesAny: splitVantaCommaList(params.ciaCategoryMatchesAny), + treatmentTypeMatchesAny: splitVantaCommaList(params.treatmentTypeMatchesAny), + inherentScoreGroupMatchesAny: splitVantaCommaList(params.inherentScoreGroupMatchesAny), + residualScoreGroupMatchesAny: splitVantaCommaList(params.residualScoreGroupMatchesAny), + reviewStatusMatchesAny: splitVantaCommaList(params.reviewStatusMatchesAny), + orderBy: params.orderBy, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + } + case 'vanta_get_risk_scenario': + return { + method: 'GET', + url: buildVantaUrl(baseUrl, `/risk-scenarios/${id(params.riskScenarioId)}`), + } + } +} + +/** + * Normalizes a successful Vanta API response body into the operation's + * documented output shape. + */ +function buildVantaOutput(params: VantaQueryBody, data: unknown): Record { + switch (params.operation) { + case 'vanta_list_frameworks': { + const { data: items, pageInfo } = getVantaListResults(data) + return { frameworks: items.map(normalizeVantaFramework), pageInfo } + } + case 'vanta_get_framework': + return { framework: normalizeVantaFrameworkDetail(asVantaRecord(data)) } + case 'vanta_list_framework_controls': + case 'vanta_list_controls': { + const { data: items, pageInfo } = getVantaListResults(data) + return { controls: items.map(normalizeVantaControl), pageInfo } + } + case 'vanta_get_control': + return { control: normalizeVantaControlDetail(asVantaRecord(data)) } + case 'vanta_list_control_tests': + case 'vanta_list_tests': { + const { data: items, pageInfo } = getVantaListResults(data) + return { tests: items.map(normalizeVantaTest), pageInfo } + } + case 'vanta_get_test': + return { test: normalizeVantaTest(asVantaRecord(data)) } + case 'vanta_list_test_entities': { + const { data: items, pageInfo } = getVantaListResults(data) + return { entities: items.map(normalizeVantaTestEntity), pageInfo } + } + case 'vanta_list_control_documents': + case 'vanta_list_documents': { + const { data: items, pageInfo } = getVantaListResults(data) + return { documents: items.map(normalizeVantaDocument), pageInfo } + } + case 'vanta_get_document': + return { document: normalizeVantaDocumentDetail(asVantaRecord(data)) } + case 'vanta_list_document_uploads': { + const { data: items, pageInfo } = getVantaListResults(data) + return { uploads: items.map(normalizeVantaUploadedFile), pageInfo } + } + case 'vanta_submit_document': + return { documentId: params.documentId, submitted: true } + case 'vanta_list_people': { + const { data: items, pageInfo } = getVantaListResults(data) + return { people: items.map(normalizeVantaPerson), pageInfo } + } + case 'vanta_get_person': + return { person: normalizeVantaPerson(asVantaRecord(data)) } + case 'vanta_list_policies': { + const { data: items, pageInfo } = getVantaListResults(data) + return { policies: items.map(normalizeVantaPolicy), pageInfo } + } + case 'vanta_get_policy': + return { policy: normalizeVantaPolicy(asVantaRecord(data)) } + case 'vanta_list_vendors': { + const { data: items, pageInfo } = getVantaListResults(data) + return { vendors: items.map(normalizeVantaVendor), pageInfo } + } + case 'vanta_get_vendor': + return { vendor: normalizeVantaVendor(asVantaRecord(data)) } + case 'vanta_list_monitored_computers': { + const { data: items, pageInfo } = getVantaListResults(data) + return { computers: items.map(normalizeVantaMonitoredComputer), pageInfo } + } + case 'vanta_list_vulnerabilities': { + const { data: items, pageInfo } = getVantaListResults(data) + return { vulnerabilities: items.map(normalizeVantaVulnerability), pageInfo } + } + case 'vanta_list_vulnerability_remediations': { + const { data: items, pageInfo } = getVantaListResults(data) + return { remediations: items.map(normalizeVantaVulnerabilityRemediation), pageInfo } + } + case 'vanta_list_vulnerable_assets': { + const { data: items, pageInfo } = getVantaListResults(data) + return { assets: items.map(normalizeVantaVulnerableAsset), pageInfo } + } + case 'vanta_get_vulnerable_asset': + return { asset: normalizeVantaVulnerableAsset(asVantaRecord(data)) } + case 'vanta_list_risk_scenarios': { + const { data: items, pageInfo } = getVantaListResults(data) + return { riskScenarios: items.map(normalizeVantaRiskScenario), pageInfo } + } + case 'vanta_get_risk_scenario': + return { riskScenario: normalizeVantaRiskScenario(asVantaRecord(data)) } + } +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Vanta query attempt`, { + error: authResult.error || 'Unauthorized', + }) + return NextResponse.json( + { success: false, error: authResult.error || 'Unauthorized' }, + { status: 401 } + ) + } + + try { + const parsed = await parseRequest(vantaQueryContract, request, {}) + if (!parsed.success) return parsed.response + const params = parsed.data.body + + const baseUrl = getVantaBaseUrl(params.region) + const scope = + params.operation === 'vanta_submit_document' ? VANTA_DOCUMENT_UPLOAD_SCOPE : VANTA_READ_SCOPE + const accessToken = await getVantaAccessToken({ + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope, + }) + + logger.info(`[${requestId}] Vanta query request`, { operation: params.operation }) + + const apiRequest = buildVantaApiRequest(baseUrl, params) + const response = await fetch(apiRequest.url, { + method: apiRequest.method, + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + cache: 'no-store', + }) + + if (!response.ok) { + const errorData: unknown = await response.json().catch(() => null) + return NextResponse.json( + { success: false, error: extractVantaError(errorData, 'Vanta request failed') }, + { status: response.status } + ) + } + + const data: unknown = response.status === 204 ? null : await response.json().catch(() => null) + return NextResponse.json({ success: true, output: buildVantaOutput(params, data) }) + } catch (error) { + const message = toError(error).message + logger.error(`[${requestId}] Vanta query failed`, { error: message }) + return NextResponse.json({ success: false, error: message }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/vanta/upload/route.ts b/apps/sim/app/api/tools/vanta/upload/route.ts new file mode 100644 index 0000000000..5e526eb9fb --- /dev/null +++ b/apps/sim/app/api/tools/vanta/upload/route.ts @@ -0,0 +1,139 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { vantaUploadContract } from '@/lib/api/contracts/tools/vanta' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { assertToolFileAccess } from '@/app/api/files/authorization' +import { + asVantaRecord, + buildVantaUrl, + extractVantaError, + getVantaAccessToken, + getVantaBaseUrl, + normalizeVantaUploadedFile, + VANTA_DOCUMENT_UPLOAD_SCOPE, +} from '@/tools/vanta/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('VantaUploadAPI') + +const MAX_UPLOAD_SIZE_BYTES = 100 * 1024 * 1024 + +function uploadSizeError(bytes: number): NextResponse { + const sizeMB = (bytes / (1024 * 1024)).toFixed(2) + return NextResponse.json( + { success: false, error: `File size (${sizeMB}MB) exceeds upload limit of 100MB` }, + { status: 400 } + ) +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + logger.warn(`[${requestId}] Unauthorized Vanta upload attempt`, { + error: authResult.error || 'Missing userId', + }) + return NextResponse.json( + { success: false, error: authResult.error || 'Unauthorized' }, + { status: 401 } + ) + } + + const parsed = await parseRequest(vantaUploadContract, request, {}) + if (!parsed.success) return parsed.response + const params = parsed.data.body + + let fileBuffer: Buffer + let fileName: string + let mimeType: string + + if (params.file) { + const userFiles = processFilesToUserFiles([params.file as RawFileInput], requestId, logger) + if (userFiles.length === 0) { + return NextResponse.json({ success: false, error: 'Invalid file input' }, { status: 400 }) + } + + const userFile = userFiles[0] + const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger) + if (denied) return denied + + if (userFile.size > MAX_UPLOAD_SIZE_BYTES) { + return uploadSizeError(userFile.size) + } + + fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + fileName = params.fileName || userFile.name + mimeType = userFile.type || 'application/octet-stream' + } else if (params.fileContent) { + fileBuffer = Buffer.from(params.fileContent, 'base64') + fileName = params.fileName || 'file' + mimeType = 'application/octet-stream' + } else { + return NextResponse.json({ success: false, error: 'File is required' }, { status: 400 }) + } + + if (fileBuffer.length > MAX_UPLOAD_SIZE_BYTES) { + return uploadSizeError(fileBuffer.length) + } + + const accessToken = await getVantaAccessToken({ + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope: VANTA_DOCUMENT_UPLOAD_SCOPE, + }) + + logger.info(`[${requestId}] Uploading file to Vanta document`, { + documentId: params.documentId, + fileName, + size: fileBuffer.length, + }) + + const formData = new FormData() + formData.append('file', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), fileName) + if (params.description) { + formData.append('description', params.description) + } + if (params.effectiveAtDate) { + formData.append('effectiveAtDate', params.effectiveAtDate) + } + + const uploadUrl = buildVantaUrl( + getVantaBaseUrl(params.region), + `/documents/${encodeURIComponent(params.documentId)}/uploads` + ) + const response = await fetch(uploadUrl, { + method: 'POST', + headers: { Authorization: `Bearer ${accessToken}` }, + body: formData, + cache: 'no-store', + }) + + const data: unknown = await response.json().catch(() => null) + if (!response.ok) { + const message = extractVantaError(data, 'Failed to upload file to Vanta document') + logger.error(`[${requestId}] Vanta upload failed`, { status: response.status, message }) + return NextResponse.json({ success: false, error: message }, { status: response.status }) + } + + logger.info(`[${requestId}] Vanta upload successful`, { documentId: params.documentId }) + + return NextResponse.json({ + success: true, + output: { upload: normalizeVantaUploadedFile(asVantaRecord(data)) }, + }) + } catch (error) { + const message = toError(error).message + logger.error(`[${requestId}] Vanta upload failed`, { error: message }) + return NextResponse.json({ success: false, error: message }, { status: 500 }) + } +}) diff --git a/apps/sim/blocks/blocks/vanta.ts b/apps/sim/blocks/blocks/vanta.ts new file mode 100644 index 0000000000..d85f7bd69d --- /dev/null +++ b/apps/sim/blocks/blocks/vanta.ts @@ -0,0 +1,1225 @@ +import { VantaIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' +import type { ToolResponse } from '@/tools/types' + +const LIST_OPERATIONS = [ + 'list_frameworks', + 'list_framework_controls', + 'list_controls', + 'list_control_tests', + 'list_control_documents', + 'list_tests', + 'list_test_entities', + 'list_documents', + 'list_document_uploads', + 'list_people', + 'list_policies', + 'list_vendors', + 'list_monitored_computers', + 'list_vulnerabilities', + 'list_vulnerability_remediations', + 'list_vulnerable_assets', + 'list_risk_scenarios', +] + +const DOCUMENT_ID_OPERATIONS = [ + 'get_document', + 'list_document_uploads', + 'upload_document_file', + 'download_document_file', + 'submit_document', +] + +const CONTROL_ID_OPERATIONS = ['get_control', 'list_control_tests', 'list_control_documents'] + +/** + * Maps a tri-state dropdown value ("any" | "true" | "false") to an optional + * boolean tool param. + */ +function triStateToBoolean(value: unknown): boolean | undefined { + if (value === true || value === 'true') return true + if (value === false || value === 'false') return false + return undefined +} + +/** Maps an "all" dropdown sentinel to undefined so no filter is sent. */ +function dropdownFilter(value: unknown): string | undefined { + return typeof value === 'string' && value !== '' && value !== 'all' ? value : undefined +} + +function optionalString(value: unknown): string | undefined { + return typeof value === 'string' && value.trim() !== '' ? value : undefined +} + +export const VantaBlock: BlockConfig = { + type: 'vanta', + name: 'Vanta', + description: 'Query compliance status and manage evidence in Vanta', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Vanta into the workflow. Monitor compliance frameworks, controls, and automated tests; find failing test entities; manage evidence documents including file upload, download, and submission; and track people, policies, vendors, monitored computers, vulnerabilities, and risk scenarios. Requires Vanta OAuth client credentials.', + category: 'tools', + integrationType: IntegrationType.Security, + docsLink: 'https://docs.sim.ai/integrations/vanta', + bgColor: '#F8F4F3', + icon: VantaIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Frameworks', id: 'list_frameworks' }, + { label: 'Get Framework', id: 'get_framework' }, + { label: 'List Framework Controls', id: 'list_framework_controls' }, + { label: 'List Controls', id: 'list_controls' }, + { label: 'Get Control', id: 'get_control' }, + { label: 'List Control Tests', id: 'list_control_tests' }, + { label: 'List Control Documents', id: 'list_control_documents' }, + { label: 'List Tests', id: 'list_tests' }, + { label: 'Get Test', id: 'get_test' }, + { label: 'List Test Entities', id: 'list_test_entities' }, + { label: 'List Documents', id: 'list_documents' }, + { label: 'Get Document', id: 'get_document' }, + { label: 'List Document Uploads', id: 'list_document_uploads' }, + { label: 'Upload Document File', id: 'upload_document_file' }, + { label: 'Download Document File', id: 'download_document_file' }, + { label: 'Submit Document', id: 'submit_document' }, + { label: 'List People', id: 'list_people' }, + { label: 'Get Person', id: 'get_person' }, + { label: 'List Policies', id: 'list_policies' }, + { label: 'Get Policy', id: 'get_policy' }, + { label: 'List Vendors', id: 'list_vendors' }, + { label: 'Get Vendor', id: 'get_vendor' }, + { label: 'List Monitored Computers', id: 'list_monitored_computers' }, + { label: 'List Vulnerabilities', id: 'list_vulnerabilities' }, + { label: 'List Vulnerability Remediations', id: 'list_vulnerability_remediations' }, + { label: 'List Vulnerable Assets', id: 'list_vulnerable_assets' }, + { label: 'Get Vulnerable Asset', id: 'get_vulnerable_asset' }, + { label: 'List Risk Scenarios', id: 'list_risk_scenarios' }, + { label: 'Get Risk Scenario', id: 'get_risk_scenario' }, + ], + value: () => 'list_frameworks', + }, + { + id: 'frameworkId', + title: 'Framework ID', + type: 'short-input', + placeholder: 'Framework ID (e.g., soc2)', + condition: { field: 'operation', value: ['get_framework', 'list_framework_controls'] }, + required: { field: 'operation', value: ['get_framework', 'list_framework_controls'] }, + }, + { + id: 'controlId', + title: 'Control ID', + type: 'short-input', + placeholder: 'Control ID', + condition: { field: 'operation', value: CONTROL_ID_OPERATIONS }, + required: { field: 'operation', value: CONTROL_ID_OPERATIONS }, + }, + { + id: 'testId', + title: 'Test ID', + type: 'short-input', + placeholder: 'Test ID (e.g., test-aws-cloudtrail-enabled)', + condition: { field: 'operation', value: ['get_test', 'list_test_entities'] }, + required: { field: 'operation', value: ['get_test', 'list_test_entities'] }, + }, + { + id: 'entityStatus', + title: 'Entity Status', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Failing', id: 'FAILING' }, + { label: 'Deactivated', id: 'DEACTIVATED' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_test_entities' }, + }, + { + id: 'documentId', + title: 'Document ID', + type: 'short-input', + placeholder: 'Document ID', + condition: { field: 'operation', value: DOCUMENT_ID_OPERATIONS }, + required: { field: 'operation', value: DOCUMENT_ID_OPERATIONS }, + }, + { + id: 'uploadedFileId', + title: 'Uploaded File ID', + type: 'short-input', + placeholder: 'Uploaded file ID (from List Document Uploads)', + condition: { field: 'operation', value: 'download_document_file' }, + required: { field: 'operation', value: 'download_document_file' }, + }, + { + id: 'uploadFile', + title: 'File', + type: 'file-upload', + canonicalParamId: 'file', + placeholder: 'Upload evidence file', + mode: 'basic', + multiple: false, + condition: { field: 'operation', value: 'upload_document_file' }, + required: { field: 'operation', value: 'upload_document_file' }, + }, + { + id: 'fileRef', + title: 'File', + type: 'short-input', + canonicalParamId: 'file', + placeholder: 'Reference file from previous blocks', + mode: 'advanced', + condition: { field: 'operation', value: 'upload_document_file' }, + required: { field: 'operation', value: 'upload_document_file' }, + }, + { + id: 'uploadFileName', + title: 'File Name', + type: 'short-input', + placeholder: 'Optional file name override', + condition: { field: 'operation', value: 'upload_document_file' }, + mode: 'advanced', + }, + { + id: 'uploadDescription', + title: 'Description', + type: 'short-input', + placeholder: 'Description of the uploaded evidence', + condition: { field: 'operation', value: 'upload_document_file' }, + }, + { + id: 'effectiveAtDate', + title: 'Effective Date', + type: 'short-input', + placeholder: 'ISO 8601 date (e.g., 2026-06-01)', + condition: { field: 'operation', value: 'upload_document_file' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 date (e.g., 2026-06-01) for when the document is effective from. Return ONLY the date string - no explanations, no extra text.', + generationType: 'timestamp', + }, + }, + { + id: 'frameworkMatchesAny', + title: 'Framework IDs', + type: 'short-input', + placeholder: 'Comma-separated framework IDs (e.g., soc2,iso27001)', + condition: { field: 'operation', value: ['list_controls', 'list_documents'] }, + }, + { + id: 'documentStatusFilter', + title: 'Document Statuses', + type: 'short-input', + placeholder: 'Comma-separated: Needs document, Needs update, Not relevant, OK', + condition: { field: 'operation', value: 'list_documents' }, + mode: 'advanced', + }, + { + id: 'statusFilter', + title: 'Test Status', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'OK', id: 'OK' }, + { label: 'Needs Attention', id: 'NEEDS_ATTENTION' }, + { label: 'In Progress', id: 'IN_PROGRESS' }, + { label: 'Deactivated', id: 'DEACTIVATED' }, + { label: 'Invalid', id: 'INVALID' }, + { label: 'Not Applicable', id: 'NOT_APPLICABLE' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_tests' }, + }, + { + id: 'frameworkFilter', + title: 'Framework ID', + type: 'short-input', + placeholder: 'Filter tests by framework ID (e.g., soc2)', + condition: { field: 'operation', value: 'list_tests' }, + }, + { + id: 'integrationFilter', + title: 'Integration ID', + type: 'short-input', + placeholder: 'Filter tests by integration ID (e.g., aws)', + condition: { field: 'operation', value: 'list_tests' }, + mode: 'advanced', + }, + { + id: 'controlFilter', + title: 'Control ID', + type: 'short-input', + placeholder: 'Filter tests by control ID', + condition: { field: 'operation', value: 'list_tests' }, + mode: 'advanced', + }, + { + id: 'ownerFilter', + title: 'Owner User ID', + type: 'short-input', + placeholder: 'Filter tests by owner user ID', + condition: { field: 'operation', value: 'list_tests' }, + mode: 'advanced', + }, + { + id: 'categoryFilter', + title: 'Test Category', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Accounts Access', id: 'ACCOUNTS_ACCESS' }, + { label: 'Account Security', id: 'ACCOUNT_SECURITY' }, + { label: 'Account Setup', id: 'ACCOUNT_SETUP' }, + { label: 'Computers', id: 'COMPUTERS' }, + { label: 'Custom', id: 'CUSTOM' }, + { label: 'Data Storage', id: 'DATA_STORAGE' }, + { label: 'Employees', id: 'EMPLOYEES' }, + { label: 'Infrastructure', id: 'INFRASTRUCTURE' }, + { label: 'IT', id: 'IT' }, + { label: 'Logging', id: 'LOGGING' }, + { label: 'Monitoring Alerts', id: 'MONITORING_ALERTS' }, + { label: 'People', id: 'PEOPLE' }, + { label: 'Policies', id: 'POLICIES' }, + { label: 'Risk Analysis', id: 'RISK_ANALYSIS' }, + { label: 'Security Alert Management', id: 'SECURITY_ALERT_MANAGEMENT' }, + { label: 'Software Development', id: 'SOFTWARE_DEVELOPMENT' }, + { label: 'Vendors', id: 'VENDORS' }, + { label: 'Vulnerability Management', id: 'VULNERABILITY_MANAGEMENT' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_tests' }, + mode: 'advanced', + }, + { + id: 'isInRollout', + title: 'In Rollout', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_tests' }, + mode: 'advanced', + }, + { + id: 'personId', + title: 'Person ID', + type: 'short-input', + placeholder: 'Person ID', + condition: { field: 'operation', value: 'get_person' }, + required: { field: 'operation', value: 'get_person' }, + }, + { + id: 'emailAndNameFilter', + title: 'Email or Name', + type: 'short-input', + placeholder: 'Filter people by email address or name', + condition: { field: 'operation', value: 'list_people' }, + }, + { + id: 'employmentStatus', + title: 'Employment Status', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Upcoming', id: 'UPCOMING' }, + { label: 'Current', id: 'CURRENT' }, + { label: 'On Leave', id: 'ON_LEAVE' }, + { label: 'Inactive', id: 'INACTIVE' }, + { label: 'Former', id: 'FORMER' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_people' }, + }, + { + id: 'groupIdsMatchesAny', + title: 'Group IDs', + type: 'short-input', + placeholder: 'Comma-separated group IDs', + condition: { field: 'operation', value: 'list_people' }, + mode: 'advanced', + }, + { + id: 'tasksSummaryStatusMatchesAny', + title: 'Task Summary Statuses', + type: 'short-input', + placeholder: 'Comma-separated: NONE, DUE_SOON, OVERDUE, COMPLETE, PAUSED, ...', + condition: { field: 'operation', value: 'list_people' }, + mode: 'advanced', + }, + { + id: 'taskTypeMatchesAny', + title: 'Task Types', + type: 'short-input', + placeholder: 'Comma-separated: COMPLETE_TRAININGS, ACCEPT_POLICIES, ...', + condition: { field: 'operation', value: 'list_people' }, + mode: 'advanced', + }, + { + id: 'taskStatusMatchesAny', + title: 'Task Statuses', + type: 'short-input', + placeholder: 'Comma-separated: COMPLETE, DUE_SOON, OVERDUE, NONE', + condition: { field: 'operation', value: 'list_people' }, + mode: 'advanced', + }, + { + id: 'policyId', + title: 'Policy ID', + type: 'short-input', + placeholder: 'Policy ID', + condition: { field: 'operation', value: 'get_policy' }, + required: { field: 'operation', value: 'get_policy' }, + }, + { + id: 'vendorId', + title: 'Vendor ID', + type: 'short-input', + placeholder: 'Vendor ID', + condition: { field: 'operation', value: 'get_vendor' }, + required: { field: 'operation', value: 'get_vendor' }, + }, + { + id: 'vendorName', + title: 'Vendor Name', + type: 'short-input', + placeholder: 'Filter vendors by name', + condition: { field: 'operation', value: 'list_vendors' }, + }, + { + id: 'vendorStatusFilter', + title: 'Vendor Statuses', + type: 'short-input', + placeholder: 'Comma-separated: MANAGED, ARCHIVED, IN_PROCUREMENT', + condition: { field: 'operation', value: 'list_vendors' }, + mode: 'advanced', + }, + { + id: 'complianceStatusFilterMatchesAny', + title: 'Compliance Issues', + type: 'short-input', + placeholder: 'Comma-separated: HD_NOT_ENCRYPTED, AV_NOT_INSTALLED, ...', + condition: { field: 'operation', value: 'list_monitored_computers' }, + mode: 'advanced', + }, + { + id: 'searchQuery', + title: 'Search Query', + type: 'short-input', + placeholder: 'Search query', + condition: { field: 'operation', value: ['list_vulnerabilities', 'list_vulnerable_assets'] }, + }, + { + id: 'severity', + title: 'Severity', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Critical', id: 'CRITICAL' }, + { label: 'High', id: 'HIGH' }, + { label: 'Medium', id: 'MEDIUM' }, + { label: 'Low', id: 'LOW' }, + ], + value: () => 'all', + condition: { + field: 'operation', + value: ['list_vulnerabilities', 'list_vulnerability_remediations'], + }, + }, + { + id: 'integrationId', + title: 'Integration ID', + type: 'short-input', + placeholder: 'Filter by integration ID', + condition: { + field: 'operation', + value: [ + 'list_vulnerabilities', + 'list_vulnerability_remediations', + 'list_vulnerable_assets', + ], + }, + mode: 'advanced', + }, + { + id: 'isFixAvailable', + title: 'Fix Available', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + }, + { + id: 'isDeactivated', + title: 'Deactivated', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + }, + { + id: 'includeVulnerabilitiesWithoutSlas', + title: 'Include Without SLAs', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + }, + { + id: 'packageIdentifier', + title: 'Package Identifier', + type: 'short-input', + placeholder: 'Filter by affected package', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + }, + { + id: 'externalVulnerabilityId', + title: 'External Vulnerability ID', + type: 'short-input', + placeholder: 'Filter by external ID (e.g., CVE-2026-1234)', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + }, + { + id: 'vulnerableAssetId', + title: 'Vulnerable Asset ID', + type: 'short-input', + placeholder: 'Vulnerable asset ID', + condition: { + field: 'operation', + value: ['list_vulnerabilities', 'get_vulnerable_asset'], + }, + required: { field: 'operation', value: 'get_vulnerable_asset' }, + }, + { + id: 'slaDeadlineAfterDate', + title: 'SLA Deadline After', + type: 'short-input', + placeholder: 'ISO 8601 date (e.g., 2026-06-01)', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 date (e.g., 2026-06-01) for the start of the SLA deadline range. Return ONLY the date string - no explanations, no extra text.', + generationType: 'timestamp', + }, + }, + { + id: 'slaDeadlineBeforeDate', + title: 'SLA Deadline Before', + type: 'short-input', + placeholder: 'ISO 8601 date (e.g., 2026-06-30)', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 date (e.g., 2026-06-30) for the end of the SLA deadline range. Return ONLY the date string - no explanations, no extra text.', + generationType: 'timestamp', + }, + }, + { + id: 'isRemediatedOnTime', + title: 'Remediated On Time', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_vulnerability_remediations' }, + mode: 'advanced', + }, + { + id: 'remediatedAfterDate', + title: 'Remediated After', + type: 'short-input', + placeholder: 'ISO 8601 date (e.g., 2026-06-01)', + condition: { field: 'operation', value: 'list_vulnerability_remediations' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 date (e.g., 2026-06-01) for the start of the remediation date range. Return ONLY the date string - no explanations, no extra text.', + generationType: 'timestamp', + }, + }, + { + id: 'remediatedBeforeDate', + title: 'Remediated Before', + type: 'short-input', + placeholder: 'ISO 8601 date (e.g., 2026-06-30)', + condition: { field: 'operation', value: 'list_vulnerability_remediations' }, + mode: 'advanced', + wandConfig: { + enabled: true, + prompt: + 'Generate an ISO 8601 date (e.g., 2026-06-30) for the end of the remediation date range. Return ONLY the date string - no explanations, no extra text.', + generationType: 'timestamp', + }, + }, + { + id: 'assetType', + title: 'Asset Type', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Server', id: 'SERVER' }, + { label: 'Serverless Function', id: 'SERVERLESS_FUNCTION' }, + { label: 'Container', id: 'CONTAINER' }, + { label: 'Container Repository', id: 'CONTAINER_REPOSITORY' }, + { label: 'Container Repository Image', id: 'CONTAINER_REPOSITORY_IMAGE' }, + { label: 'Code Repository', id: 'CODE_REPOSITORY' }, + { label: 'Manifest File', id: 'MANIFEST_FILE' }, + { label: 'Workstation', id: 'WORKSTATION' }, + { label: 'Other', id: 'OTHER' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_vulnerable_assets' }, + }, + { + id: 'assetExternalAccountId', + title: 'External Account ID', + type: 'short-input', + placeholder: 'Filter assets by external account ID', + condition: { field: 'operation', value: 'list_vulnerable_assets' }, + mode: 'advanced', + }, + { + id: 'riskScenarioId', + title: 'Risk Scenario ID', + type: 'short-input', + placeholder: 'Risk scenario ID', + condition: { field: 'operation', value: 'get_risk_scenario' }, + required: { field: 'operation', value: 'get_risk_scenario' }, + }, + { + id: 'searchString', + title: 'Search', + type: 'short-input', + placeholder: 'Search risk scenarios', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + }, + { + id: 'riskType', + title: 'Scenario Type', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Risk Scenario', id: 'Risk Scenario' }, + { label: 'Enterprise Risk', id: 'Enterprise Risk' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'includeIgnored', + title: 'Include Ignored', + type: 'dropdown', + options: [ + { label: 'Any', id: 'any' }, + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'any', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'ownerMatchesAny', + title: 'Owner Emails', + type: 'short-input', + placeholder: 'Comma-separated owner emails', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'categoryMatchesAny', + title: 'Risk Categories', + type: 'short-input', + placeholder: 'Comma-separated risk categories', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'ciaCategoryMatchesAny', + title: 'CIA Categories', + type: 'short-input', + placeholder: 'Comma-separated: Confidentiality, Integrity, Availability', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'treatmentTypeMatchesAny', + title: 'Treatments', + type: 'short-input', + placeholder: 'Comma-separated: Mitigate, Transfer, Avoid, Accept', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'inherentScoreGroupMatchesAny', + title: 'Inherent Score Groups', + type: 'short-input', + placeholder: 'Comma-separated: Very low, Low, Med, High, Critical', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'residualScoreGroupMatchesAny', + title: 'Residual Score Groups', + type: 'short-input', + placeholder: 'Comma-separated: Very low, Low, Med, High, Critical', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'reviewStatusMatchesAny', + title: 'Review Statuses', + type: 'short-input', + placeholder: 'Comma-separated: APPROVED, DRAFT, NOT_REVIEWED, ...', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'orderBy', + title: 'Order By', + type: 'dropdown', + options: [ + { label: 'Default', id: 'all' }, + { label: 'Description', id: 'description' }, + { label: 'Created At', id: 'createdAt' }, + ], + value: () => 'all', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + mode: 'advanced', + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: '10 (max 100)', + condition: { field: 'operation', value: LIST_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'pageCursor', + title: 'Page Cursor', + type: 'short-input', + placeholder: 'endCursor from the previous response', + condition: { field: 'operation', value: LIST_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'region', + title: 'Region', + type: 'dropdown', + options: [ + { label: 'US (api.vanta.com)', id: 'us' }, + { label: 'Gov / FedRAMP (api.vanta-gov.com)', id: 'gov' }, + ], + value: () => 'us', + mode: 'advanced', + }, + { + id: 'clientId', + title: 'Client ID', + type: 'short-input', + placeholder: 'Vanta OAuth application client ID', + required: true, + }, + { + id: 'clientSecret', + title: 'Client Secret', + type: 'short-input', + placeholder: 'Vanta OAuth application client secret', + password: true, + required: true, + }, + ], + tools: { + access: [ + 'vanta_list_frameworks', + 'vanta_get_framework', + 'vanta_list_framework_controls', + 'vanta_list_controls', + 'vanta_get_control', + 'vanta_list_control_tests', + 'vanta_list_control_documents', + 'vanta_list_tests', + 'vanta_get_test', + 'vanta_list_test_entities', + 'vanta_list_documents', + 'vanta_get_document', + 'vanta_list_document_uploads', + 'vanta_upload_document_file', + 'vanta_download_document_file', + 'vanta_submit_document', + 'vanta_list_people', + 'vanta_get_person', + 'vanta_list_policies', + 'vanta_get_policy', + 'vanta_list_vendors', + 'vanta_get_vendor', + 'vanta_list_monitored_computers', + 'vanta_list_vulnerabilities', + 'vanta_list_vulnerability_remediations', + 'vanta_list_vulnerable_assets', + 'vanta_get_vulnerable_asset', + 'vanta_list_risk_scenarios', + 'vanta_get_risk_scenario', + ], + config: { + tool: (params) => `vanta_${params.operation}`, + params: (params) => { + const { operation, ...rest } = params + const result: Record = {} + + result.region = dropdownFilter(rest.region) ?? 'us' + + if (LIST_OPERATIONS.includes(operation)) { + result.pageSize = + rest.pageSize !== undefined && rest.pageSize !== '' && rest.pageSize !== null + ? Number(rest.pageSize) + : undefined + result.pageCursor = optionalString(rest.pageCursor) + } + + switch (operation) { + case 'list_test_entities': + result.entityStatus = dropdownFilter(rest.entityStatus) + break + case 'upload_document_file': { + const normalizedFile = normalizeFileInput(rest.file, { single: true }) + if (normalizedFile) result.file = normalizedFile + result.fileName = optionalString(rest.uploadFileName) + result.description = optionalString(rest.uploadDescription) + result.effectiveAtDate = optionalString(rest.effectiveAtDate) + break + } + case 'list_documents': + result.statusMatchesAny = optionalString(rest.documentStatusFilter) + break + case 'list_tests': + result.statusFilter = dropdownFilter(rest.statusFilter) + result.categoryFilter = dropdownFilter(rest.categoryFilter) + result.isInRollout = triStateToBoolean(rest.isInRollout) + break + case 'list_people': + result.employmentStatus = dropdownFilter(rest.employmentStatus) + break + case 'list_vendors': + result.name = optionalString(rest.vendorName) + result.statusMatchesAny = optionalString(rest.vendorStatusFilter) + break + case 'list_vulnerabilities': + result.q = optionalString(rest.searchQuery) + result.severity = dropdownFilter(rest.severity) + result.isFixAvailable = triStateToBoolean(rest.isFixAvailable) + result.isDeactivated = triStateToBoolean(rest.isDeactivated) + result.includeVulnerabilitiesWithoutSlas = triStateToBoolean( + rest.includeVulnerabilitiesWithoutSlas + ) + break + case 'list_vulnerability_remediations': + result.severity = dropdownFilter(rest.severity) + result.isRemediatedOnTime = triStateToBoolean(rest.isRemediatedOnTime) + break + case 'list_vulnerable_assets': + result.q = optionalString(rest.searchQuery) + result.assetType = dropdownFilter(rest.assetType) + break + case 'list_risk_scenarios': + result.type = dropdownFilter(rest.riskType) + result.includeIgnored = triStateToBoolean(rest.includeIgnored) + result.orderBy = dropdownFilter(rest.orderBy) + break + default: + break + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + clientId: { type: 'string', description: 'Vanta OAuth application client ID' }, + clientSecret: { type: 'string', description: 'Vanta OAuth application client secret' }, + region: { type: 'string', description: 'Vanta API region (us or gov)' }, + frameworkId: { type: 'string', description: 'Framework ID' }, + controlId: { type: 'string', description: 'Control ID' }, + testId: { type: 'string', description: 'Test ID' }, + entityStatus: { type: 'string', description: 'Test entity status filter' }, + documentId: { type: 'string', description: 'Document ID' }, + uploadedFileId: { type: 'string', description: 'Uploaded file ID' }, + file: { type: 'json', description: 'Evidence file to upload' }, + uploadFileName: { type: 'string', description: 'Optional file name override' }, + uploadDescription: { type: 'string', description: 'Description of the uploaded evidence' }, + effectiveAtDate: { type: 'string', description: 'Effective date of the document (ISO 8601)' }, + frameworkMatchesAny: { type: 'string', description: 'Comma-separated framework ID filters' }, + documentStatusFilter: { type: 'string', description: 'Comma-separated document statuses' }, + statusFilter: { type: 'string', description: 'Test status filter' }, + frameworkFilter: { type: 'string', description: 'Framework ID filter for tests' }, + integrationFilter: { type: 'string', description: 'Integration ID filter for tests' }, + controlFilter: { type: 'string', description: 'Control ID filter for tests' }, + ownerFilter: { type: 'string', description: 'Owner user ID filter for tests' }, + categoryFilter: { type: 'string', description: 'Test category filter' }, + isInRollout: { type: 'string', description: 'Rollout filter (any, true, or false)' }, + personId: { type: 'string', description: 'Person ID' }, + emailAndNameFilter: { type: 'string', description: 'Email or name filter for people' }, + employmentStatus: { type: 'string', description: 'Employment status filter' }, + groupIdsMatchesAny: { type: 'string', description: 'Comma-separated group ID filters' }, + tasksSummaryStatusMatchesAny: { + type: 'string', + description: 'Comma-separated task summary status filters', + }, + taskTypeMatchesAny: { type: 'string', description: 'Comma-separated task type filters' }, + taskStatusMatchesAny: { type: 'string', description: 'Comma-separated task status filters' }, + policyId: { type: 'string', description: 'Policy ID' }, + vendorId: { type: 'string', description: 'Vendor ID' }, + vendorName: { type: 'string', description: 'Vendor name filter' }, + vendorStatusFilter: { type: 'string', description: 'Comma-separated vendor statuses' }, + complianceStatusFilterMatchesAny: { + type: 'string', + description: 'Comma-separated computer compliance issue filters', + }, + searchQuery: { type: 'string', description: 'Search query' }, + severity: { type: 'string', description: 'Severity filter' }, + integrationId: { type: 'string', description: 'Integration ID filter' }, + isFixAvailable: { type: 'string', description: 'Fix availability filter (any, true, false)' }, + isDeactivated: { type: 'string', description: 'Deactivation filter (any, true, false)' }, + includeVulnerabilitiesWithoutSlas: { + type: 'string', + description: 'Include vulnerabilities without SLAs (any, true, false)', + }, + packageIdentifier: { type: 'string', description: 'Package identifier filter' }, + externalVulnerabilityId: { type: 'string', description: 'External vulnerability ID filter' }, + vulnerableAssetId: { type: 'string', description: 'Vulnerable asset ID' }, + slaDeadlineAfterDate: { type: 'string', description: 'SLA deadline range start (ISO 8601)' }, + slaDeadlineBeforeDate: { type: 'string', description: 'SLA deadline range end (ISO 8601)' }, + isRemediatedOnTime: { + type: 'string', + description: 'On-time remediation filter (any, true, false)', + }, + remediatedAfterDate: { type: 'string', description: 'Remediation range start (ISO 8601)' }, + remediatedBeforeDate: { type: 'string', description: 'Remediation range end (ISO 8601)' }, + assetType: { type: 'string', description: 'Vulnerable asset type filter' }, + assetExternalAccountId: { type: 'string', description: 'External account ID filter' }, + riskScenarioId: { type: 'string', description: 'Risk scenario ID' }, + searchString: { type: 'string', description: 'Search string for risk scenarios' }, + riskType: { type: 'string', description: 'Risk scenario type filter' }, + includeIgnored: { type: 'string', description: 'Include ignored scenarios (any, true, false)' }, + ownerMatchesAny: { type: 'string', description: 'Comma-separated owner email filters' }, + categoryMatchesAny: { type: 'string', description: 'Comma-separated risk category filters' }, + ciaCategoryMatchesAny: { type: 'string', description: 'Comma-separated CIA category filters' }, + treatmentTypeMatchesAny: { type: 'string', description: 'Comma-separated treatment filters' }, + inherentScoreGroupMatchesAny: { + type: 'string', + description: 'Comma-separated inherent score group filters', + }, + residualScoreGroupMatchesAny: { + type: 'string', + description: 'Comma-separated residual score group filters', + }, + reviewStatusMatchesAny: { + type: 'string', + description: 'Comma-separated review status filters', + }, + orderBy: { type: 'string', description: 'Risk scenario sort field (description or createdAt)' }, + pageSize: { type: 'number', description: 'Maximum number of items per page (1-100)' }, + pageCursor: { type: 'string', description: 'Pagination cursor from the previous response' }, + }, + outputs: { + frameworks: { + type: 'array', + description: 'Frameworks in the Vanta account', + condition: { field: 'operation', value: 'list_frameworks' }, + }, + framework: { + type: 'json', + description: 'The requested framework with requirement categories', + condition: { field: 'operation', value: 'get_framework' }, + }, + controls: { + type: 'array', + description: 'Controls matching the filters', + condition: { field: 'operation', value: ['list_controls', 'list_framework_controls'] }, + }, + control: { + type: 'json', + description: 'The requested control', + condition: { field: 'operation', value: 'get_control' }, + }, + tests: { + type: 'array', + description: 'Tests matching the filters', + condition: { field: 'operation', value: ['list_tests', 'list_control_tests'] }, + }, + test: { + type: 'json', + description: 'The requested test', + condition: { field: 'operation', value: 'get_test' }, + }, + entities: { + type: 'array', + description: 'Failing or deactivated entities for the test', + condition: { field: 'operation', value: 'list_test_entities' }, + }, + documents: { + type: 'array', + description: 'Documents matching the filters', + condition: { field: 'operation', value: ['list_documents', 'list_control_documents'] }, + }, + document: { + type: 'json', + description: 'The requested document', + condition: { field: 'operation', value: 'get_document' }, + }, + uploads: { + type: 'array', + description: 'Files uploaded to the document', + condition: { field: 'operation', value: 'list_document_uploads' }, + }, + upload: { + type: 'json', + description: 'Metadata of the uploaded file', + condition: { field: 'operation', value: 'upload_document_file' }, + }, + file: { + type: 'file', + description: 'Downloaded file stored in execution files', + condition: { field: 'operation', value: 'download_document_file' }, + }, + name: { + type: 'string', + description: 'Name of the downloaded file', + condition: { field: 'operation', value: 'download_document_file' }, + }, + mimeType: { + type: 'string', + description: 'MIME type of the downloaded file', + condition: { field: 'operation', value: 'download_document_file' }, + }, + size: { + type: 'number', + description: 'Size of the downloaded file in bytes', + condition: { field: 'operation', value: 'download_document_file' }, + }, + documentId: { + type: 'string', + description: 'ID of the submitted document', + condition: { field: 'operation', value: 'submit_document' }, + }, + submitted: { + type: 'boolean', + description: 'Whether the document collection was submitted', + condition: { field: 'operation', value: 'submit_document' }, + }, + people: { + type: 'array', + description: 'People matching the filters', + condition: { field: 'operation', value: 'list_people' }, + }, + person: { + type: 'json', + description: 'The requested person', + condition: { field: 'operation', value: 'get_person' }, + }, + policies: { + type: 'array', + description: 'Policies in the Vanta account', + condition: { field: 'operation', value: 'list_policies' }, + }, + policy: { + type: 'json', + description: 'The requested policy', + condition: { field: 'operation', value: 'get_policy' }, + }, + vendors: { + type: 'array', + description: 'Vendors matching the filters', + condition: { field: 'operation', value: 'list_vendors' }, + }, + vendor: { + type: 'json', + description: 'The requested vendor', + condition: { field: 'operation', value: 'get_vendor' }, + }, + computers: { + type: 'array', + description: 'Monitored computers matching the filters', + condition: { field: 'operation', value: 'list_monitored_computers' }, + }, + vulnerabilities: { + type: 'array', + description: 'Vulnerabilities matching the filters', + condition: { field: 'operation', value: 'list_vulnerabilities' }, + }, + remediations: { + type: 'array', + description: 'Vulnerability remediations matching the filters', + condition: { field: 'operation', value: 'list_vulnerability_remediations' }, + }, + assets: { + type: 'array', + description: 'Vulnerable assets matching the filters', + condition: { field: 'operation', value: 'list_vulnerable_assets' }, + }, + asset: { + type: 'json', + description: 'The requested vulnerable asset', + condition: { field: 'operation', value: 'get_vulnerable_asset' }, + }, + riskScenarios: { + type: 'array', + description: 'Risk scenarios matching the filters', + condition: { field: 'operation', value: 'list_risk_scenarios' }, + }, + riskScenario: { + type: 'json', + description: 'The requested risk scenario', + condition: { field: 'operation', value: 'get_risk_scenario' }, + }, + pageInfo: { + type: 'json', + description: 'Cursor pagination info (endCursor, hasNextPage) for the returned page', + condition: { field: 'operation', value: LIST_OPERATIONS }, + }, + }, +} + +export const VantaBlockMeta = { + tags: ['monitoring', 'automation', 'document-processing'], + templates: [ + { + icon: VantaIcon, + title: 'Vanta failing test alerts', + prompt: + 'Create a scheduled workflow that lists Vanta tests with status NEEDS_ATTENTION each morning, fetches the failing entities for each test, and posts a remediation digest to a Slack channel.', + modules: ['scheduled', 'workflows'], + category: 'operations', + tags: ['monitoring', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: VantaIcon, + title: 'Vanta evidence uploader', + prompt: + 'Build a workflow that takes a generated report file, uploads it as evidence to the matching Vanta document with a description and effective date, and then submits the document collection for review.', + modules: ['files', 'workflows'], + category: 'operations', + tags: ['automation', 'document-processing'], + }, + { + icon: VantaIcon, + title: 'Vanta vulnerability SLA watcher', + prompt: + 'Create a scheduled workflow that lists Vanta vulnerabilities with SLA deadlines in the next 7 days, groups them by severity and vulnerable asset, and emails the security team a prioritized remediation list.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'engineering', + tags: ['monitoring', 'automation'], + alsoIntegrations: ['gmail'], + }, + { + icon: VantaIcon, + title: 'Vanta compliance status report', + prompt: + 'Build a scheduled workflow that lists Vanta frameworks with their control and test completion counts, has an agent summarize progress and gaps per framework, and writes a weekly compliance report to a table.', + modules: ['scheduled', 'agent', 'tables', 'workflows'], + category: 'operations', + tags: ['reporting', 'monitoring'], + }, + { + icon: VantaIcon, + title: 'Vanta onboarding task chaser', + prompt: + 'Create a scheduled workflow that lists current Vanta people with overdue security tasks, and sends each person a direct Slack message listing what they still need to complete.', + modules: ['scheduled', 'workflows'], + category: 'operations', + tags: ['automation', 'people'], + alsoIntegrations: ['slack'], + }, + { + icon: VantaIcon, + title: 'Vanta vendor review pipeline', + prompt: + 'Build a scheduled workflow that lists Vanta vendors whose next security review is due within 30 days, looks up each vendor’s risk levels and contract dates, and creates a review task in the team’s project tracker.', + modules: ['scheduled', 'workflows'], + category: 'operations', + tags: ['automation', 'vendor-management'], + alsoIntegrations: ['linear'], + }, + { + icon: VantaIcon, + title: 'Vanta compliance Q&A agent', + prompt: + 'Create an agent that answers compliance questions by querying Vanta: it can look up framework progress, control status, failing tests and their entities, policy approval status, and risk scenarios, and grounds every answer in the returned data.', + modules: ['agent', 'workflows'], + category: 'productivity', + tags: ['agentic', 'research'], + }, + ], + skills: [ + { + name: 'triage-failing-vanta-tests', + description: + 'Find failing Vanta tests, pull their failing entities, and produce a prioritized remediation list.', + content: + '# Triage Failing Vanta Tests\n\nTurn the current Vanta test status into an actionable remediation list.\n\n## Steps\n1. Use List Tests with Test Status set to Needs Attention to find failing tests. Narrow with Framework ID or Integration ID if asked.\n2. For each failing test, use List Test Entities with Entity Status set to Failing to get the exact resources that fail.\n3. Read each test’s failure description and remediation description from the output to explain what is wrong and how to fix it.\n4. Group results by test category and order by the number of failing entities.\n\n## Output\nReturn a prioritized list: test name, why it fails, the failing resources, and the documented remediation steps.', + }, + { + name: 'upload-vanta-evidence', + description: + 'Attach an evidence file to a Vanta document and submit the collection for review.', + content: + '# Upload Evidence to Vanta\n\nAttach a file to the right evidence document and make it visible to auditors.\n\n## Steps\n1. Use List Documents (filter by framework or status "Needs document" / "Needs update") to find the target document and note its ID.\n2. Use Upload Document File with the document ID, the file, a clear Description (e.g., "Q3 access review evidence"), and optionally an Effective Date.\n3. Use Submit Document with the same document ID so the evidence moves out of draft and becomes visible to auditors.\n4. Confirm with Get Document that the upload status is now OK.\n\n## Output\nReturn the uploaded file metadata and the document’s final status.', + }, + { + name: 'vanta-compliance-snapshot', + description: 'Summarize framework, control, and test completion across a Vanta account.', + content: + '# Vanta Compliance Snapshot\n\nProduce a concise status report across all frameworks.\n\n## Steps\n1. Use List Frameworks to get every framework with its control, document, and test completion counts.\n2. For frameworks that are behind, use List Framework Controls and Get Control to find controls with failing or missing evidence.\n3. Use List Tests with Test Status set to Needs Attention to count open issues per framework.\n4. Compute completion percentages from the numeric outputs (numControlsCompleted / numControlsTotal, etc.).\n\n## Output\nReturn a per-framework table: completion percentages, failing test count, and the controls that need attention.', + }, + { + name: 'vanta-vulnerability-sla-report', + description: + 'List Vanta vulnerabilities approaching their SLA deadlines with affected assets.', + content: + '# Vulnerabilities Approaching SLA\n\nFind what must be remediated soon and where.\n\n## Steps\n1. Use List Vulnerabilities with SLA Deadline Before set to the cutoff date (e.g., 7 days from now) and SLA Deadline After set to today.\n2. Narrow with Severity (CRITICAL or HIGH first) and Fix Available set to Yes for quick wins.\n3. For each vulnerability, use Get Vulnerable Asset with its asset ID to identify the affected server, repository, or workstation.\n4. Use List Vulnerability Remediations with Remediated On Time set to No to report recent SLA misses.\n\n## Output\nReturn vulnerabilities grouped by severity with remediate-by dates, fixed versions when available, and the affected assets.', + }, + { + name: 'vanta-people-task-audit', + description: 'Find people with overdue security tasks in Vanta and what each still owes.', + content: + '# Audit Outstanding Security Tasks\n\nIdentify who is blocking compliance and why.\n\n## Steps\n1. Use List People with Task Summary Statuses set to OVERDUE,DUE_SOON and Employment Status set to Current.\n2. Read each person’s tasksSummary output for the due date, and use Task Types to narrow to a specific obligation (e.g., COMPLETE_TRAININGS or ACCEPT_POLICIES) when asked.\n3. Use Get Person for any individual to confirm employment, group membership, and leave status before escalating.\n\n## Output\nReturn each person’s name, email, overdue items, and due dates, ordered by how overdue they are.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index efc25757b3..80d833dd17 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -290,6 +290,7 @@ import { TwilioSMSBlock, TwilioSMSBlockMeta } from '@/blocks/blocks/twilio' import { TwilioVoiceBlock, TwilioVoiceBlockMeta } from '@/blocks/blocks/twilio_voice' import { TypeformBlock, TypeformBlockMeta } from '@/blocks/blocks/typeform' import { UpstashBlock, UpstashBlockMeta } from '@/blocks/blocks/upstash' +import { VantaBlock, VantaBlockMeta } from '@/blocks/blocks/vanta' import { VariablesBlock } from '@/blocks/blocks/variables' import { VercelBlock, VercelBlockMeta } from '@/blocks/blocks/vercel' import { @@ -590,6 +591,7 @@ const BLOCK_REGISTRY: Record = { twilio_voice: TwilioVoiceBlock, typeform: TypeformBlock, upstash: UpstashBlock, + vanta: VantaBlock, variables: VariablesBlock, vercel: VercelBlock, video_generator: VideoGeneratorBlock, @@ -836,6 +838,7 @@ const BLOCK_META_REGISTRY: Record = { twilio_voice: TwilioVoiceBlockMeta, typeform: TypeformBlockMeta, upstash: UpstashBlockMeta, + vanta: VantaBlockMeta, vercel: VercelBlockMeta, wealthbox: WealthboxBlockMeta, webflow: WebflowBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 1368eb93b3..573eb40256 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -7555,6 +7555,54 @@ export function OnePasswordIcon(props: SVGProps) { ) } +export function VantaIcon(props: SVGProps) { + return ( + + + + + + + + + + + + + + + + + + + + + ) +} + export function VercelIcon(props: SVGProps) { return ( z.string().trim().min(1, `${label} is required`) + +const listFrameworksSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_frameworks'), +}) + +const getFrameworkSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_framework'), + frameworkId: requiredId('Framework ID'), +}) + +const listFrameworkControlsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_framework_controls'), + frameworkId: requiredId('Framework ID'), +}) + +const listControlsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_controls'), + frameworkMatchesAny: z.string().optional(), +}) + +const getControlSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_control'), + controlId: requiredId('Control ID'), +}) + +const listControlTestsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_control_tests'), + controlId: requiredId('Control ID'), +}) + +const listControlDocumentsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_control_documents'), + controlId: requiredId('Control ID'), +}) + +const listTestsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_tests'), + statusFilter: z + .enum(['OK', 'DEACTIVATED', 'NEEDS_ATTENTION', 'IN_PROGRESS', 'INVALID', 'NOT_APPLICABLE']) + .optional(), + frameworkFilter: z.string().optional(), + integrationFilter: z.string().optional(), + controlFilter: z.string().optional(), + ownerFilter: z.string().optional(), + categoryFilter: z + .enum([ + 'ACCOUNTS_ACCESS', + 'ACCOUNT_SECURITY', + 'ACCOUNT_SETUP', + 'COMPUTERS', + 'CUSTOM', + 'DATA_STORAGE', + 'EMPLOYEES', + 'INFRASTRUCTURE', + 'IT', + 'LOGGING', + 'MONITORING_ALERTS', + 'PEOPLE', + 'POLICIES', + 'RISK_ANALYSIS', + 'SECURITY_ALERT_MANAGEMENT', + 'SOFTWARE_DEVELOPMENT', + 'VENDORS', + 'VULNERABILITY_MANAGEMENT', + ]) + .optional(), + isInRollout: z.boolean().optional(), +}) + +const getTestSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_test'), + testId: requiredId('Test ID'), +}) + +const listTestEntitiesSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_test_entities'), + testId: requiredId('Test ID'), + entityStatus: z.enum(['FAILING', 'DEACTIVATED']).optional(), +}) + +const listDocumentsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_documents'), + frameworkMatchesAny: z.string().optional(), + statusMatchesAny: z.string().optional(), +}) + +const getDocumentSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_document'), + documentId: requiredId('Document ID'), +}) + +const listDocumentUploadsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_document_uploads'), + documentId: requiredId('Document ID'), +}) + +const submitDocumentSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_submit_document'), + documentId: requiredId('Document ID'), +}) + +const listPeopleSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_people'), + emailAndNameFilter: z.string().optional(), + employmentStatus: z.enum(['UPCOMING', 'CURRENT', 'ON_LEAVE', 'INACTIVE', 'FORMER']).optional(), + groupIdsMatchesAny: z.string().optional(), + tasksSummaryStatusMatchesAny: z.string().optional(), + taskTypeMatchesAny: z.string().optional(), + taskStatusMatchesAny: z.string().optional(), +}) + +const getPersonSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_person'), + personId: requiredId('Person ID'), +}) + +const listPoliciesSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_policies'), +}) + +const getPolicySchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_policy'), + policyId: requiredId('Policy ID'), +}) + +const listVendorsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_vendors'), + name: z.string().optional(), + statusMatchesAny: z.string().optional(), +}) + +const getVendorSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_vendor'), + vendorId: requiredId('Vendor ID'), +}) + +const listMonitoredComputersSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_monitored_computers'), + complianceStatusFilterMatchesAny: z.string().optional(), +}) + +const listVulnerabilitiesSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_vulnerabilities'), + q: z.string().optional(), + severity: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional(), + isFixAvailable: z.boolean().optional(), + isDeactivated: z.boolean().optional(), + includeVulnerabilitiesWithoutSlas: z.boolean().optional(), + packageIdentifier: z.string().optional(), + externalVulnerabilityId: z.string().optional(), + integrationId: z.string().optional(), + vulnerableAssetId: z.string().optional(), + slaDeadlineAfterDate: z.string().optional(), + slaDeadlineBeforeDate: z.string().optional(), +}) + +const listVulnerabilityRemediationsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_vulnerability_remediations'), + integrationId: z.string().optional(), + severity: z.enum(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']).optional(), + isRemediatedOnTime: z.boolean().optional(), + remediatedAfterDate: z.string().optional(), + remediatedBeforeDate: z.string().optional(), +}) + +const listVulnerableAssetsSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_vulnerable_assets'), + q: z.string().optional(), + integrationId: z.string().optional(), + assetType: z + .enum([ + 'SERVER', + 'SERVERLESS_FUNCTION', + 'CONTAINER', + 'CONTAINER_REPOSITORY', + 'CONTAINER_REPOSITORY_IMAGE', + 'CODE_REPOSITORY', + 'MANIFEST_FILE', + 'WORKSTATION', + 'OTHER', + ]) + .optional(), + assetExternalAccountId: z.string().optional(), +}) + +const getVulnerableAssetSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_vulnerable_asset'), + vulnerableAssetId: requiredId('Vulnerable asset ID'), +}) + +const listRiskScenariosSchema = vantaListBaseBodySchema.extend({ + operation: z.literal('vanta_list_risk_scenarios'), + searchString: z.string().optional(), + includeIgnored: z.boolean().optional(), + type: z.enum(['Risk Scenario', 'Enterprise Risk']).optional(), + ownerMatchesAny: z.string().optional(), + categoryMatchesAny: z.string().optional(), + ciaCategoryMatchesAny: z.string().optional(), + treatmentTypeMatchesAny: z.string().optional(), + inherentScoreGroupMatchesAny: z.string().optional(), + residualScoreGroupMatchesAny: z.string().optional(), + reviewStatusMatchesAny: z.string().optional(), + orderBy: z.enum(['description', 'createdAt']).optional(), +}) + +const getRiskScenarioSchema = vantaBaseBodySchema.extend({ + operation: z.literal('vanta_get_risk_scenario'), + riskScenarioId: requiredId('Risk scenario ID'), +}) + +export const vantaQueryBodySchema = z.discriminatedUnion('operation', [ + listFrameworksSchema, + getFrameworkSchema, + listFrameworkControlsSchema, + listControlsSchema, + getControlSchema, + listControlTestsSchema, + listControlDocumentsSchema, + listTestsSchema, + getTestSchema, + listTestEntitiesSchema, + listDocumentsSchema, + getDocumentSchema, + listDocumentUploadsSchema, + submitDocumentSchema, + listPeopleSchema, + getPersonSchema, + listPoliciesSchema, + getPolicySchema, + listVendorsSchema, + getVendorSchema, + listMonitoredComputersSchema, + listVulnerabilitiesSchema, + listVulnerabilityRemediationsSchema, + listVulnerableAssetsSchema, + getVulnerableAssetSchema, + listRiskScenariosSchema, + getRiskScenarioSchema, +]) + +const vantaQueryOutputSchema = z.union([ + z.object({ frameworks: z.array(vantaFrameworkSchema), pageInfo: vantaPageInfoSchema }), + z.object({ framework: vantaFrameworkDetailSchema }), + z.object({ controls: z.array(vantaControlSchema), pageInfo: vantaPageInfoSchema }), + z.object({ control: vantaControlDetailSchema }), + z.object({ tests: z.array(vantaTestSchema), pageInfo: vantaPageInfoSchema }), + z.object({ test: vantaTestSchema }), + z.object({ entities: z.array(vantaTestEntitySchema), pageInfo: vantaPageInfoSchema }), + z.object({ documents: z.array(vantaDocumentSchema), pageInfo: vantaPageInfoSchema }), + z.object({ document: vantaDocumentDetailSchema }), + z.object({ uploads: z.array(vantaUploadedFileSchema), pageInfo: vantaPageInfoSchema }), + z.object({ documentId: z.string(), submitted: z.boolean() }), + z.object({ people: z.array(vantaPersonSchema), pageInfo: vantaPageInfoSchema }), + z.object({ person: vantaPersonSchema }), + z.object({ policies: z.array(vantaPolicySchema), pageInfo: vantaPageInfoSchema }), + z.object({ policy: vantaPolicySchema }), + z.object({ vendors: z.array(vantaVendorSchema), pageInfo: vantaPageInfoSchema }), + z.object({ vendor: vantaVendorSchema }), + z.object({ computers: z.array(vantaMonitoredComputerSchema), pageInfo: vantaPageInfoSchema }), + z.object({ + vulnerabilities: z.array(vantaVulnerabilitySchema), + pageInfo: vantaPageInfoSchema, + }), + z.object({ + remediations: z.array(vantaVulnerabilityRemediationSchema), + pageInfo: vantaPageInfoSchema, + }), + z.object({ assets: z.array(vantaVulnerableAssetSchema), pageInfo: vantaPageInfoSchema }), + z.object({ asset: vantaVulnerableAssetSchema }), + z.object({ riskScenarios: z.array(vantaRiskScenarioSchema), pageInfo: vantaPageInfoSchema }), + z.object({ riskScenario: vantaRiskScenarioSchema }), +]) + +const vantaQueryResponseSchema = z.object({ + success: z.literal(true), + output: vantaQueryOutputSchema, +}) + +export const vantaQueryContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/vanta/query', + body: vantaQueryBodySchema, + response: { mode: 'json', schema: vantaQueryResponseSchema }, +}) + +export const vantaUploadBodySchema = vantaBaseBodySchema.extend({ + documentId: requiredId('Document ID'), + file: FileInputSchema.optional().nullable(), + fileContent: z.string().nullish(), + fileName: z.string().nullish(), + description: z.string().nullish(), + effectiveAtDate: z.string().nullish(), +}) + +const vantaUploadResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ upload: vantaUploadedFileSchema }), +}) + +export const vantaUploadContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/vanta/upload', + body: vantaUploadBodySchema, + response: { mode: 'json', schema: vantaUploadResponseSchema }, +}) + +export const vantaDownloadBodySchema = vantaBaseBodySchema.extend({ + documentId: requiredId('Document ID'), + uploadedFileId: requiredId('Uploaded file ID'), +}) + +const vantaDownloadResponseSchema = z.object({ + success: z.literal(true), + output: z.object({ + file: z.object({ + name: z.string(), + mimeType: z.string(), + data: z.string(), + size: z.number(), + }), + name: z.string(), + mimeType: z.string(), + size: z.number(), + }), +}) + +export const vantaDownloadContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/vanta/download', + body: vantaDownloadBodySchema, + response: { mode: 'json', schema: vantaDownloadResponseSchema }, +}) + +export type VantaQueryBody = ContractBody +export type VantaQueryBodyInput = ContractBodyInput +export type VantaQueryResponse = ContractJsonResponse +export type VantaUploadBody = ContractBody +export type VantaUploadBodyInput = ContractBodyInput +export type VantaUploadResponse = ContractJsonResponse +export type VantaDownloadBody = ContractBody +export type VantaDownloadBodyInput = ContractBodyInput +export type VantaDownloadResponse = ContractJsonResponse diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 4b71d06bd0..7aeb59d753 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -206,6 +206,7 @@ import { TwilioIcon, TypeformIcon, UpstashIcon, + VantaIcon, VercelIcon, WealthboxIcon, WebflowIcon, @@ -430,6 +431,7 @@ export const blockTypeToIconMap: Record = { twilio_voice: TwilioIcon, typeform: TypeformIcon, upstash: UpstashIcon, + vanta: VantaIcon, vercel: VercelIcon, wealthbox: WealthboxIcon, webflow: WebflowIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index b12e346a77..39763e9106 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -15666,6 +15666,141 @@ "integrationType": "databases", "tags": ["cloud"] }, + { + "type": "vanta", + "slug": "vanta", + "name": "Vanta", + "description": "Query compliance status and manage evidence in Vanta", + "longDescription": "Integrate Vanta into the workflow. Monitor compliance frameworks, controls, and automated tests; find failing test entities; manage evidence documents including file upload, download, and submission; and track people, policies, vendors, monitored computers, vulnerabilities, and risk scenarios. Requires Vanta OAuth client credentials.", + "bgColor": "#F8F4F3", + "iconName": "VantaIcon", + "docsUrl": "https://docs.sim.ai/integrations/vanta", + "operations": [ + { + "name": "List Frameworks", + "description": "List the compliance frameworks (e.g., SOC 2, ISO 27001) available in a Vanta account with completion counts" + }, + { + "name": "Get Framework", + "description": "Get a Vanta compliance framework by ID, including its requirement categories and mapped controls" + }, + { + "name": "List Framework Controls", + "description": "List the controls that belong to a specific Vanta compliance framework" + }, + { + "name": "List Controls", + "description": "List the security controls in a Vanta account, optionally filtered by framework" + }, + { + "name": "Get Control", + "description": "Get a Vanta security control by ID, including its status and evidence pass/fail counts" + }, + { + "name": "List Control Tests", + "description": "List the automated tests mapped to a specific Vanta control" + }, + { + "name": "List Control Documents", + "description": "List the evidence documents mapped to a specific Vanta control" + }, + { + "name": "List Tests", + "description": "List the automated compliance tests in a Vanta account, with filters for status, framework, integration, control, owner, and category" + }, + { + "name": "Get Test", + "description": "Get a Vanta automated compliance test by ID, including its status and remediation info" + }, + { + "name": "List Test Entities", + "description": "List the failing or deactivated resource entities for a specific Vanta test, useful for finding exactly which resources need remediation" + }, + { + "name": "List Documents", + "description": "List the evidence documents in a Vanta account, optionally filtered by framework or document status" + }, + { + "name": "Get Document", + "description": "Get a Vanta evidence document by ID, including its renewal schedule and deactivation status" + }, + { + "name": "List Document Uploads", + "description": "List the files uploaded to a specific Vanta evidence document" + }, + { + "name": "Upload Document File", + "description": "Upload an evidence file to a Vanta document. Requires credentials with the vanta-api.documents:upload scope." + }, + { + "name": "Download Document File", + "description": "Download a file previously uploaded to a Vanta evidence document and store it in execution files" + }, + { + "name": "Submit Document", + "description": "Submit a Vanta document collection for review so uploaded evidence becomes visible to auditors. Requires credentials with write access." + }, + { + "name": "List People", + "description": "List the people tracked in a Vanta account with employment status, group membership, and security task completion" + }, + { + "name": "Get Person", + "description": "Get a person tracked in Vanta by ID, including employment, leave, and security task status" + }, + { + "name": "List Policies", + "description": "List the security policies in a Vanta account with approval status and version info" + }, + { + "name": "Get Policy", + "description": "Get a Vanta security policy by ID, including its approval status and latest approved version documents" + }, + { + "name": "List Vendors", + "description": "List the vendors tracked in a Vanta account with risk levels, contract dates, and security review schedules" + }, + { + "name": "Get Vendor", + "description": "Get a Vanta vendor by ID, including risk levels, contract details, and authentication info" + }, + { + "name": "List Monitored Computers", + "description": "List the monitored computers in a Vanta account with screenlock, disk encryption, password manager, and antivirus check outcomes" + }, + { + "name": "List Vulnerabilities", + "description": "List the vulnerabilities detected across a Vanta account with filters for severity, fixability, SLA deadlines, package, and integration" + }, + { + "name": "List Vulnerability Remediations", + "description": "List remediated vulnerabilities in a Vanta account with detection, SLA deadline, and remediation dates" + }, + { + "name": "List Vulnerable Assets", + "description": "List the assets associated with vulnerabilities in a Vanta account (servers, repositories, workstations, and more)" + }, + { + "name": "Get Vulnerable Asset", + "description": "Get a vulnerable asset in Vanta by ID, including the scanners reporting it and per-scanner asset details" + }, + { + "name": "List Risk Scenarios", + "description": "List the risk scenarios in a Vanta risk register with likelihood/impact scores, treatment decisions, and review status" + }, + { + "name": "Get Risk Scenario", + "description": "Get a Vanta risk scenario by ID, including its scores, treatment decision, and review status" + } + ], + "operationCount": 29, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "security", + "tags": ["monitoring", "automation", "document-processing"] + }, { "type": "vercel", "slug": "vercel", diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f197492d44..3ca9537b16 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -3375,6 +3375,37 @@ import { upstashRedisSetTool, upstashRedisTtlTool, } from '@/tools/upstash' +import { + vantaDownloadDocumentFileTool, + vantaGetControlTool, + vantaGetDocumentTool, + vantaGetFrameworkTool, + vantaGetPersonTool, + vantaGetPolicyTool, + vantaGetRiskScenarioTool, + vantaGetTestTool, + vantaGetVendorTool, + vantaGetVulnerableAssetTool, + vantaListControlDocumentsTool, + vantaListControlsTool, + vantaListControlTestsTool, + vantaListDocumentsTool, + vantaListDocumentUploadsTool, + vantaListFrameworkControlsTool, + vantaListFrameworksTool, + vantaListMonitoredComputersTool, + vantaListPeopleTool, + vantaListPoliciesTool, + vantaListRiskScenariosTool, + vantaListTestEntitiesTool, + vantaListTestsTool, + vantaListVendorsTool, + vantaListVulnerabilitiesTool, + vantaListVulnerabilityRemediationsTool, + vantaListVulnerableAssetsTool, + vantaSubmitDocumentTool, + vantaUploadDocumentFileTool, +} from '@/tools/vanta' import { vercelAddDomainTool, vercelAddProjectDomainTool, @@ -5511,6 +5542,35 @@ export const tools: Record = { trigger_dev_get_waitpoint_token: triggerDevGetWaitpointTokenTool, trigger_dev_list_waitpoint_tokens: triggerDevListWaitpointTokensTool, trigger_dev_list_timezones: triggerDevListTimezonesTool, + vanta_list_frameworks: vantaListFrameworksTool, + vanta_get_framework: vantaGetFrameworkTool, + vanta_list_framework_controls: vantaListFrameworkControlsTool, + vanta_list_controls: vantaListControlsTool, + vanta_get_control: vantaGetControlTool, + vanta_list_control_tests: vantaListControlTestsTool, + vanta_list_control_documents: vantaListControlDocumentsTool, + vanta_list_tests: vantaListTestsTool, + vanta_get_test: vantaGetTestTool, + vanta_list_test_entities: vantaListTestEntitiesTool, + vanta_list_documents: vantaListDocumentsTool, + vanta_get_document: vantaGetDocumentTool, + vanta_list_document_uploads: vantaListDocumentUploadsTool, + vanta_upload_document_file: vantaUploadDocumentFileTool, + vanta_download_document_file: vantaDownloadDocumentFileTool, + vanta_submit_document: vantaSubmitDocumentTool, + vanta_list_people: vantaListPeopleTool, + vanta_get_person: vantaGetPersonTool, + vanta_list_policies: vantaListPoliciesTool, + vanta_get_policy: vantaGetPolicyTool, + vanta_list_vendors: vantaListVendorsTool, + vanta_get_vendor: vantaGetVendorTool, + vanta_list_monitored_computers: vantaListMonitoredComputersTool, + vanta_list_vulnerabilities: vantaListVulnerabilitiesTool, + vanta_list_vulnerability_remediations: vantaListVulnerabilityRemediationsTool, + vanta_list_vulnerable_assets: vantaListVulnerableAssetsTool, + vanta_get_vulnerable_asset: vantaGetVulnerableAssetTool, + vanta_list_risk_scenarios: vantaListRiskScenariosTool, + vanta_get_risk_scenario: vantaGetRiskScenarioTool, vercel_list_deployments: vercelListDeploymentsTool, vercel_get_deployment: vercelGetDeploymentTool, vercel_create_deployment: vercelCreateDeploymentTool, diff --git a/apps/sim/tools/vanta/download_document_file.ts b/apps/sim/tools/vanta/download_document_file.ts new file mode 100644 index 0000000000..6da54a8100 --- /dev/null +++ b/apps/sim/tools/vanta/download_document_file.ts @@ -0,0 +1,74 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VantaDownloadDocumentFileParams, + VantaDownloadDocumentFileResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse } from '@/tools/vanta/utils' + +export const vantaDownloadDocumentFileTool: ToolConfig< + VantaDownloadDocumentFileParams, + VantaDownloadDocumentFileResponse +> = { + id: 'vanta_download_document_file', + name: 'Vanta Download Document File', + description: + 'Download a file previously uploaded to a Vanta evidence document and store it in execution files', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + documentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the document', + }, + uploadedFileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the uploaded file (from List Document Uploads)', + }, + }, + + request: { + url: '/api/tools/vanta/download', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + documentId: params.documentId, + uploadedFileId: params.uploadedFileId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to download Vanta document file' + ), + + outputs: { + file: { type: 'file', description: 'Downloaded file stored in execution files' }, + name: { type: 'string', description: 'Name of the downloaded file' }, + mimeType: { type: 'string', description: 'MIME type of the downloaded file' }, + size: { type: 'number', description: 'Size of the downloaded file in bytes' }, + }, +} diff --git a/apps/sim/tools/vanta/get_control.ts b/apps/sim/tools/vanta/get_control.ts new file mode 100644 index 0000000000..a403d60c2f --- /dev/null +++ b/apps/sim/tools/vanta/get_control.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_CONTROL_DETAIL_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetControlParams, VantaGetControlResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetControlTool: ToolConfig = { + id: 'vanta_get_control', + name: 'Vanta Get Control', + description: + 'Get a Vanta security control by ID, including its status and evidence pass/fail counts', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + controlId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the control', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_control', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + controlId: params.controlId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta control' + ), + + outputs: { + control: { + type: 'json', + description: 'The requested control with status and evidence counts', + properties: VANTA_CONTROL_DETAIL_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_document.ts b/apps/sim/tools/vanta/get_document.ts new file mode 100644 index 0000000000..37a3482ac6 --- /dev/null +++ b/apps/sim/tools/vanta/get_document.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_DOCUMENT_DETAIL_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetDocumentParams, VantaGetDocumentResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetDocumentTool: ToolConfig = { + id: 'vanta_get_document', + name: 'Vanta Get Document', + description: + 'Get a Vanta evidence document by ID, including its renewal schedule and deactivation status', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + documentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the document', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_document', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + documentId: params.documentId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta document' + ), + + outputs: { + document: { + type: 'json', + description: 'The requested document', + properties: VANTA_DOCUMENT_DETAIL_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_framework.ts b/apps/sim/tools/vanta/get_framework.ts new file mode 100644 index 0000000000..92c6a36618 --- /dev/null +++ b/apps/sim/tools/vanta/get_framework.ts @@ -0,0 +1,65 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_FRAMEWORK_DETAIL_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetFrameworkParams, VantaGetFrameworkResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetFrameworkTool: ToolConfig = + { + id: 'vanta_get_framework', + name: 'Vanta Get Framework', + description: + 'Get a Vanta compliance framework by ID, including its requirement categories and mapped controls', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + frameworkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the framework (e.g., soc2)', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_framework', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + frameworkId: params.frameworkId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta framework' + ), + + outputs: { + framework: { + type: 'json', + description: 'The requested framework with requirement categories', + properties: VANTA_FRAMEWORK_DETAIL_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/vanta/get_person.ts b/apps/sim/tools/vanta/get_person.ts new file mode 100644 index 0000000000..5cdb08006c --- /dev/null +++ b/apps/sim/tools/vanta/get_person.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_PERSON_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetPersonParams, VantaGetPersonResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetPersonTool: ToolConfig = { + id: 'vanta_get_person', + name: 'Vanta Get Person', + description: + 'Get a person tracked in Vanta by ID, including employment, leave, and security task status', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + personId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the person', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_person', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + personId: params.personId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta person' + ), + + outputs: { + person: { + type: 'json', + description: 'The requested person', + properties: VANTA_PERSON_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_policy.ts b/apps/sim/tools/vanta/get_policy.ts new file mode 100644 index 0000000000..51c36d8f66 --- /dev/null +++ b/apps/sim/tools/vanta/get_policy.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_POLICY_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetPolicyParams, VantaGetPolicyResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetPolicyTool: ToolConfig = { + id: 'vanta_get_policy', + name: 'Vanta Get Policy', + description: + 'Get a Vanta security policy by ID, including its approval status and latest approved version documents', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + policyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the policy', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_policy', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + policyId: params.policyId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta policy' + ), + + outputs: { + policy: { + type: 'json', + description: 'The requested policy', + properties: VANTA_POLICY_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_risk_scenario.ts b/apps/sim/tools/vanta/get_risk_scenario.ts new file mode 100644 index 0000000000..7a6396498d --- /dev/null +++ b/apps/sim/tools/vanta/get_risk_scenario.ts @@ -0,0 +1,67 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_RISK_SCENARIO_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetRiskScenarioParams, VantaGetRiskScenarioResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetRiskScenarioTool: ToolConfig< + VantaGetRiskScenarioParams, + VantaGetRiskScenarioResponse +> = { + id: 'vanta_get_risk_scenario', + name: 'Vanta Get Risk Scenario', + description: + 'Get a Vanta risk scenario by ID, including its scores, treatment decision, and review status', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + riskScenarioId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the risk scenario', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_risk_scenario', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + riskScenarioId: params.riskScenarioId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta risk scenario' + ), + + outputs: { + riskScenario: { + type: 'json', + description: 'The requested risk scenario', + properties: VANTA_RISK_SCENARIO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_test.ts b/apps/sim/tools/vanta/get_test.ts new file mode 100644 index 0000000000..e161d48ff1 --- /dev/null +++ b/apps/sim/tools/vanta/get_test.ts @@ -0,0 +1,62 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_TEST_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetTestParams, VantaGetTestResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetTestTool: ToolConfig = { + id: 'vanta_get_test', + name: 'Vanta Get Test', + description: + 'Get a Vanta automated compliance test by ID, including its status and remediation info', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + testId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the test (e.g., test-aws-cloudtrail-enabled)', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_test', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + testId: params.testId, + }), + }, + + transformResponse: createVantaTransformResponse('Failed to get Vanta test'), + + outputs: { + test: { + type: 'json', + description: 'The requested test', + properties: VANTA_TEST_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_vendor.ts b/apps/sim/tools/vanta/get_vendor.ts new file mode 100644 index 0000000000..8cb0b967ad --- /dev/null +++ b/apps/sim/tools/vanta/get_vendor.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_VENDOR_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { VantaGetVendorParams, VantaGetVendorResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetVendorTool: ToolConfig = { + id: 'vanta_get_vendor', + name: 'Vanta Get Vendor', + description: + 'Get a Vanta vendor by ID, including risk levels, contract details, and authentication info', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + vendorId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the vendor', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_vendor', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + vendorId: params.vendorId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta vendor' + ), + + outputs: { + vendor: { + type: 'json', + description: 'The requested vendor', + properties: VANTA_VENDOR_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/get_vulnerable_asset.ts b/apps/sim/tools/vanta/get_vulnerable_asset.ts new file mode 100644 index 0000000000..4830c2e0c9 --- /dev/null +++ b/apps/sim/tools/vanta/get_vulnerable_asset.ts @@ -0,0 +1,70 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_VULNERABLE_ASSET_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { + VantaGetVulnerableAssetParams, + VantaGetVulnerableAssetResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaGetVulnerableAssetTool: ToolConfig< + VantaGetVulnerableAssetParams, + VantaGetVulnerableAssetResponse +> = { + id: 'vanta_get_vulnerable_asset', + name: 'Vanta Get Vulnerable Asset', + description: + 'Get a vulnerable asset in Vanta by ID, including the scanners reporting it and per-scanner asset details', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + vulnerableAssetId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the vulnerable asset', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_get_vulnerable_asset', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + vulnerableAssetId: params.vulnerableAssetId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to get Vanta vulnerable asset' + ), + + outputs: { + asset: { + type: 'json', + description: 'The requested vulnerable asset', + properties: VANTA_VULNERABLE_ASSET_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/index.ts b/apps/sim/tools/vanta/index.ts new file mode 100644 index 0000000000..aafc1e06b9 --- /dev/null +++ b/apps/sim/tools/vanta/index.ts @@ -0,0 +1,30 @@ +export { vantaDownloadDocumentFileTool } from './download_document_file' +export { vantaGetControlTool } from './get_control' +export { vantaGetDocumentTool } from './get_document' +export { vantaGetFrameworkTool } from './get_framework' +export { vantaGetPersonTool } from './get_person' +export { vantaGetPolicyTool } from './get_policy' +export { vantaGetRiskScenarioTool } from './get_risk_scenario' +export { vantaGetTestTool } from './get_test' +export { vantaGetVendorTool } from './get_vendor' +export { vantaGetVulnerableAssetTool } from './get_vulnerable_asset' +export { vantaListControlDocumentsTool } from './list_control_documents' +export { vantaListControlTestsTool } from './list_control_tests' +export { vantaListControlsTool } from './list_controls' +export { vantaListDocumentUploadsTool } from './list_document_uploads' +export { vantaListDocumentsTool } from './list_documents' +export { vantaListFrameworkControlsTool } from './list_framework_controls' +export { vantaListFrameworksTool } from './list_frameworks' +export { vantaListMonitoredComputersTool } from './list_monitored_computers' +export { vantaListPeopleTool } from './list_people' +export { vantaListPoliciesTool } from './list_policies' +export { vantaListRiskScenariosTool } from './list_risk_scenarios' +export { vantaListTestEntitiesTool } from './list_test_entities' +export { vantaListTestsTool } from './list_tests' +export { vantaListVendorsTool } from './list_vendors' +export { vantaListVulnerabilitiesTool } from './list_vulnerabilities' +export { vantaListVulnerabilityRemediationsTool } from './list_vulnerability_remediations' +export { vantaListVulnerableAssetsTool } from './list_vulnerable_assets' +export { vantaSubmitDocumentTool } from './submit_document' +export * from './types' +export { vantaUploadDocumentFileTool } from './upload_document_file' diff --git a/apps/sim/tools/vanta/list_control_documents.ts b/apps/sim/tools/vanta/list_control_documents.ts new file mode 100644 index 0000000000..bcbd5f419b --- /dev/null +++ b/apps/sim/tools/vanta/list_control_documents.ts @@ -0,0 +1,94 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_DOCUMENT_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListControlDocumentsParams, + VantaListDocumentsResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListControlDocumentsTool: ToolConfig< + VantaListControlDocumentsParams, + VantaListDocumentsResponse +> = { + id: 'vanta_list_control_documents', + name: 'Vanta List Control Documents', + description: 'List the evidence documents mapped to a specific Vanta control', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + controlId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the control', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_control_documents', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + controlId: params.controlId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta control documents' + ), + + outputs: { + documents: { + type: 'array', + description: 'Documents mapped to the control', + items: { type: 'object', properties: VANTA_DOCUMENT_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_control_tests.ts b/apps/sim/tools/vanta/list_control_tests.ts new file mode 100644 index 0000000000..fee673f0f4 --- /dev/null +++ b/apps/sim/tools/vanta/list_control_tests.ts @@ -0,0 +1,91 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_TEST_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListControlTestsParams, VantaListTestsResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListControlTestsTool: ToolConfig< + VantaListControlTestsParams, + VantaListTestsResponse +> = { + id: 'vanta_list_control_tests', + name: 'Vanta List Control Tests', + description: 'List the automated tests mapped to a specific Vanta control', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + controlId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the control', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_control_tests', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + controlId: params.controlId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta control tests' + ), + + outputs: { + tests: { + type: 'array', + description: 'Tests mapped to the control', + items: { type: 'object', properties: VANTA_TEST_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_controls.ts b/apps/sim/tools/vanta/list_controls.ts new file mode 100644 index 0000000000..d9d78d68e2 --- /dev/null +++ b/apps/sim/tools/vanta/list_controls.ts @@ -0,0 +1,89 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_CONTROL_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListControlsParams, VantaListControlsResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListControlsTool: ToolConfig = + { + id: 'vanta_list_controls', + name: 'Vanta List Controls', + description: 'List the security controls in a Vanta account, optionally filtered by framework', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + frameworkMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated framework IDs to filter controls by (e.g., soc2,iso27001)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_controls', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + frameworkMatchesAny: params.frameworkMatchesAny, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta controls' + ), + + outputs: { + controls: { + type: 'array', + description: 'Controls matching the filters', + items: { type: 'object', properties: VANTA_CONTROL_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/vanta/list_document_uploads.ts b/apps/sim/tools/vanta/list_document_uploads.ts new file mode 100644 index 0000000000..3b31953c7c --- /dev/null +++ b/apps/sim/tools/vanta/list_document_uploads.ts @@ -0,0 +1,94 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_UPLOADED_FILE_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListDocumentUploadsParams, + VantaListDocumentUploadsResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListDocumentUploadsTool: ToolConfig< + VantaListDocumentUploadsParams, + VantaListDocumentUploadsResponse +> = { + id: 'vanta_list_document_uploads', + name: 'Vanta List Document Uploads', + description: 'List the files uploaded to a specific Vanta evidence document', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + documentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the document', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_document_uploads', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + documentId: params.documentId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta document uploads' + ), + + outputs: { + uploads: { + type: 'array', + description: 'Files uploaded to the document', + items: { type: 'object', properties: VANTA_UPLOADED_FILE_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_documents.ts b/apps/sim/tools/vanta/list_documents.ts new file mode 100644 index 0000000000..a794b4b116 --- /dev/null +++ b/apps/sim/tools/vanta/list_documents.ts @@ -0,0 +1,100 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_DOCUMENT_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListDocumentsParams, VantaListDocumentsResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListDocumentsTool: ToolConfig< + VantaListDocumentsParams, + VantaListDocumentsResponse +> = { + id: 'vanta_list_documents', + name: 'Vanta List Documents', + description: + 'List the evidence documents in a Vanta account, optionally filtered by framework or document status', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + frameworkMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated framework IDs to filter documents by (e.g., soc2,iso27001)', + }, + statusMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated document statuses to filter by: "Needs document", "Needs update", "Not relevant", "OK"', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_documents', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + frameworkMatchesAny: params.frameworkMatchesAny, + statusMatchesAny: params.statusMatchesAny, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta documents' + ), + + outputs: { + documents: { + type: 'array', + description: 'Documents matching the filters', + items: { type: 'object', properties: VANTA_DOCUMENT_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_framework_controls.ts b/apps/sim/tools/vanta/list_framework_controls.ts new file mode 100644 index 0000000000..385875a20f --- /dev/null +++ b/apps/sim/tools/vanta/list_framework_controls.ts @@ -0,0 +1,94 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_CONTROL_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListControlsResponse, + VantaListFrameworkControlsParams, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListFrameworkControlsTool: ToolConfig< + VantaListFrameworkControlsParams, + VantaListControlsResponse +> = { + id: 'vanta_list_framework_controls', + name: 'Vanta List Framework Controls', + description: 'List the controls that belong to a specific Vanta compliance framework', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + frameworkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the framework (e.g., soc2)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_framework_controls', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + frameworkId: params.frameworkId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta framework controls' + ), + + outputs: { + controls: { + type: 'array', + description: 'Controls belonging to the framework', + items: { type: 'object', properties: VANTA_CONTROL_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_frameworks.ts b/apps/sim/tools/vanta/list_frameworks.ts new file mode 100644 index 0000000000..31f9d17184 --- /dev/null +++ b/apps/sim/tools/vanta/list_frameworks.ts @@ -0,0 +1,85 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_FRAMEWORK_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListFrameworksParams, VantaListFrameworksResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListFrameworksTool: ToolConfig< + VantaListFrameworksParams, + VantaListFrameworksResponse +> = { + id: 'vanta_list_frameworks', + name: 'Vanta List Frameworks', + description: + 'List the compliance frameworks (e.g., SOC 2, ISO 27001) available in a Vanta account with completion counts', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_frameworks', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta frameworks' + ), + + outputs: { + frameworks: { + type: 'array', + description: 'Frameworks in the Vanta account', + items: { type: 'object', properties: VANTA_FRAMEWORK_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_monitored_computers.ts b/apps/sim/tools/vanta/list_monitored_computers.ts new file mode 100644 index 0000000000..48e6da5a88 --- /dev/null +++ b/apps/sim/tools/vanta/list_monitored_computers.ts @@ -0,0 +1,96 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_MONITORED_COMPUTER_OUTPUT_PROPERTIES, + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListMonitoredComputersParams, + VantaListMonitoredComputersResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListMonitoredComputersTool: ToolConfig< + VantaListMonitoredComputersParams, + VantaListMonitoredComputersResponse +> = { + id: 'vanta_list_monitored_computers', + name: 'Vanta List Monitored Computers', + description: + 'List the monitored computers in a Vanta account with screenlock, disk encryption, password manager, and antivirus check outcomes', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + complianceStatusFilterMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated compliance issues to filter by: PWM_NOT_INSTALLED, HD_NOT_ENCRYPTED, AV_NOT_INSTALLED, SCREENLOCK_NOT_CONFIGURED, LAST_CHECK_OVER_14_DAYS', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_monitored_computers', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + complianceStatusFilterMatchesAny: params.complianceStatusFilterMatchesAny, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta monitored computers' + ), + + outputs: { + computers: { + type: 'array', + description: 'Monitored computers matching the filters', + items: { type: 'object', properties: VANTA_MONITORED_COMPUTER_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_people.ts b/apps/sim/tools/vanta/list_people.ts new file mode 100644 index 0000000000..7cbe553f79 --- /dev/null +++ b/apps/sim/tools/vanta/list_people.ts @@ -0,0 +1,126 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_PERSON_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListPeopleParams, VantaListPeopleResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListPeopleTool: ToolConfig = { + id: 'vanta_list_people', + name: 'Vanta List People', + description: + 'List the people tracked in a Vanta account with employment status, group membership, and security task completion', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + emailAndNameFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter people by email address or name', + }, + employmentStatus: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by employment status: UPCOMING, CURRENT, ON_LEAVE, INACTIVE, or FORMER', + }, + groupIdsMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated group IDs to filter people by', + }, + tasksSummaryStatusMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated task summary statuses to filter by: NONE, DUE_SOON, OVERDUE, COMPLETE, PAUSED, OFFBOARDING_DUE_SOON, OFFBOARDING_OVERDUE, OFFBOARDING_COMPLETE', + }, + taskTypeMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated task types to filter by: COMPLETE_TRAININGS, ACCEPT_POLICIES, COMPLETE_CUSTOM_TASKS, COMPLETE_CUSTOM_OFFBOARDING_TASKS, INSTALL_DEVICE_MONITORING, COMPLETE_BACKGROUND_CHECKS', + }, + taskStatusMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated task statuses to filter by: COMPLETE, DUE_SOON, OVERDUE, NONE', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_people', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + emailAndNameFilter: params.emailAndNameFilter, + employmentStatus: params.employmentStatus, + groupIdsMatchesAny: params.groupIdsMatchesAny, + tasksSummaryStatusMatchesAny: params.tasksSummaryStatusMatchesAny, + taskTypeMatchesAny: params.taskTypeMatchesAny, + taskStatusMatchesAny: params.taskStatusMatchesAny, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta people' + ), + + outputs: { + people: { + type: 'array', + description: 'People matching the filters', + items: { type: 'object', properties: VANTA_PERSON_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_policies.ts b/apps/sim/tools/vanta/list_policies.ts new file mode 100644 index 0000000000..fa475a65e5 --- /dev/null +++ b/apps/sim/tools/vanta/list_policies.ts @@ -0,0 +1,83 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_POLICY_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListPoliciesParams, VantaListPoliciesResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListPoliciesTool: ToolConfig = + { + id: 'vanta_list_policies', + name: 'Vanta List Policies', + description: + 'List the security policies in a Vanta account with approval status and version info', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_policies', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta policies' + ), + + outputs: { + policies: { + type: 'array', + description: 'Policies in the Vanta account', + items: { type: 'object', properties: VANTA_POLICY_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/vanta/list_risk_scenarios.ts b/apps/sim/tools/vanta/list_risk_scenarios.ts new file mode 100644 index 0000000000..13494a4a10 --- /dev/null +++ b/apps/sim/tools/vanta/list_risk_scenarios.ts @@ -0,0 +1,169 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_RISK_SCENARIO_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListRiskScenariosParams, + VantaListRiskScenariosResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListRiskScenariosTool: ToolConfig< + VantaListRiskScenariosParams, + VantaListRiskScenariosResponse +> = { + id: 'vanta_list_risk_scenarios', + name: 'Vanta List Risk Scenarios', + description: + 'List the risk scenarios in a Vanta risk register with likelihood/impact scores, treatment decisions, and review status', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + searchString: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search string to filter risk scenarios', + }, + includeIgnored: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include ignored risk scenarios', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by scenario type: "Risk Scenario" or "Enterprise Risk"', + }, + ownerMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated owner emails to filter by', + }, + categoryMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated risk categories to filter by', + }, + ciaCategoryMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated CIA categories to filter by: Confidentiality, Integrity, Availability', + }, + treatmentTypeMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated treatments to filter by: Mitigate, Transfer, Avoid, Accept', + }, + inherentScoreGroupMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated inherent score groups to filter by: "Very low", Low, Med, High, Critical', + }, + residualScoreGroupMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated residual score groups to filter by: "Very low", Low, Med, High, Critical', + }, + reviewStatusMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated review statuses to filter by: APPROVED, DRAFT, NOT_REVIEWED, AWAITING_SUBMISSION, PENDING_APPROVAL, REQUESTED_CHANGES', + }, + orderBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to order results by: description or createdAt', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_risk_scenarios', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + searchString: params.searchString, + includeIgnored: params.includeIgnored, + type: params.type, + ownerMatchesAny: params.ownerMatchesAny, + categoryMatchesAny: params.categoryMatchesAny, + ciaCategoryMatchesAny: params.ciaCategoryMatchesAny, + treatmentTypeMatchesAny: params.treatmentTypeMatchesAny, + inherentScoreGroupMatchesAny: params.inherentScoreGroupMatchesAny, + residualScoreGroupMatchesAny: params.residualScoreGroupMatchesAny, + reviewStatusMatchesAny: params.reviewStatusMatchesAny, + orderBy: params.orderBy, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta risk scenarios' + ), + + outputs: { + riskScenarios: { + type: 'array', + description: 'Risk scenarios matching the filters', + items: { type: 'object', properties: VANTA_RISK_SCENARIO_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_test_entities.ts b/apps/sim/tools/vanta/list_test_entities.ts new file mode 100644 index 0000000000..f07b7108d3 --- /dev/null +++ b/apps/sim/tools/vanta/list_test_entities.ts @@ -0,0 +1,102 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_TEST_ENTITY_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListTestEntitiesParams, + VantaListTestEntitiesResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListTestEntitiesTool: ToolConfig< + VantaListTestEntitiesParams, + VantaListTestEntitiesResponse +> = { + id: 'vanta_list_test_entities', + name: 'Vanta List Test Entities', + description: + 'List the failing or deactivated resource entities for a specific Vanta test, useful for finding exactly which resources need remediation', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + testId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the test (e.g., test-aws-cloudtrail-enabled)', + }, + entityStatus: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter entities by status: FAILING or DEACTIVATED', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_test_entities', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + testId: params.testId, + entityStatus: params.entityStatus, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta test entities' + ), + + outputs: { + entities: { + type: 'array', + description: 'Resource entities for the test', + items: { type: 'object', properties: VANTA_TEST_ENTITY_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_tests.ts b/apps/sim/tools/vanta/list_tests.ts new file mode 100644 index 0000000000..1844f6eef8 --- /dev/null +++ b/apps/sim/tools/vanta/list_tests.ts @@ -0,0 +1,133 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_TEST_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListTestsParams, VantaListTestsResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListTestsTool: ToolConfig = { + id: 'vanta_list_tests', + name: 'Vanta List Tests', + description: + 'List the automated compliance tests in a Vanta account, with filters for status, framework, integration, control, owner, and category', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + statusFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by test status: OK, DEACTIVATED, NEEDS_ATTENTION, IN_PROGRESS, INVALID, or NOT_APPLICABLE', + }, + frameworkFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by framework ID (e.g., soc2)', + }, + integrationFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by integration ID (e.g., aws)', + }, + controlFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by control ID', + }, + ownerFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by owner user ID', + }, + categoryFilter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by test category (e.g., ACCOUNTS_ACCESS, COMPUTERS, INFRASTRUCTURE, POLICIES, VULNERABILITY_MANAGEMENT)', + }, + isInRollout: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by whether the test is in rollout', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_tests', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + statusFilter: params.statusFilter, + frameworkFilter: params.frameworkFilter, + integrationFilter: params.integrationFilter, + controlFilter: params.controlFilter, + ownerFilter: params.ownerFilter, + categoryFilter: params.categoryFilter, + isInRollout: params.isInRollout, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta tests' + ), + + outputs: { + tests: { + type: 'array', + description: 'Tests matching the filters', + items: { type: 'object', properties: VANTA_TEST_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_vendors.ts b/apps/sim/tools/vanta/list_vendors.ts new file mode 100644 index 0000000000..1d1e550981 --- /dev/null +++ b/apps/sim/tools/vanta/list_vendors.ts @@ -0,0 +1,97 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_VENDOR_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { VantaListVendorsParams, VantaListVendorsResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListVendorsTool: ToolConfig = { + id: 'vanta_list_vendors', + name: 'Vanta List Vendors', + description: + 'List the vendors tracked in a Vanta account with risk levels, contract dates, and security review schedules', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter vendors by name', + }, + statusMatchesAny: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated vendor statuses to filter by: MANAGED, ARCHIVED, IN_PROCUREMENT', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_vendors', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + name: params.name, + statusMatchesAny: params.statusMatchesAny, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta vendors' + ), + + outputs: { + vendors: { + type: 'array', + description: 'Vendors matching the filters', + items: { type: 'object', properties: VANTA_VENDOR_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_vulnerabilities.ts b/apps/sim/tools/vanta/list_vulnerabilities.ts new file mode 100644 index 0000000000..489b523e71 --- /dev/null +++ b/apps/sim/tools/vanta/list_vulnerabilities.ts @@ -0,0 +1,165 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_VULNERABILITY_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListVulnerabilitiesParams, + VantaListVulnerabilitiesResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListVulnerabilitiesTool: ToolConfig< + VantaListVulnerabilitiesParams, + VantaListVulnerabilitiesResponse +> = { + id: 'vanta_list_vulnerabilities', + name: 'Vanta List Vulnerabilities', + description: + 'List the vulnerabilities detected across a Vanta account with filters for severity, fixability, SLA deadlines, package, and integration', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + q: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search query for vulnerabilities', + }, + severity: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by severity: LOW, MEDIUM, HIGH, or CRITICAL', + }, + isFixAvailable: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by whether a fix is available', + }, + isDeactivated: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by whether vulnerability monitoring is deactivated', + }, + includeVulnerabilitiesWithoutSlas: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Include vulnerabilities that have no SLA deadline', + }, + packageIdentifier: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the affected package identifier', + }, + externalVulnerabilityId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by external vulnerability ID (e.g., a CVE identifier)', + }, + integrationId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the integration that detected the vulnerability', + }, + vulnerableAssetId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the vulnerable asset ID', + }, + slaDeadlineAfterDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include vulnerabilities with an SLA deadline after this ISO 8601 date', + }, + slaDeadlineBeforeDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include vulnerabilities with an SLA deadline before this ISO 8601 date', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_vulnerabilities', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + q: params.q, + severity: params.severity, + isFixAvailable: params.isFixAvailable, + isDeactivated: params.isDeactivated, + includeVulnerabilitiesWithoutSlas: params.includeVulnerabilitiesWithoutSlas, + packageIdentifier: params.packageIdentifier, + externalVulnerabilityId: params.externalVulnerabilityId, + integrationId: params.integrationId, + vulnerableAssetId: params.vulnerableAssetId, + slaDeadlineAfterDate: params.slaDeadlineAfterDate, + slaDeadlineBeforeDate: params.slaDeadlineBeforeDate, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta vulnerabilities' + ), + + outputs: { + vulnerabilities: { + type: 'array', + description: 'Vulnerabilities matching the filters', + items: { type: 'object', properties: VANTA_VULNERABILITY_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_vulnerability_remediations.ts b/apps/sim/tools/vanta/list_vulnerability_remediations.ts new file mode 100644 index 0000000000..7745d3914b --- /dev/null +++ b/apps/sim/tools/vanta/list_vulnerability_remediations.ts @@ -0,0 +1,123 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_VULNERABILITY_REMEDIATION_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListVulnerabilityRemediationsParams, + VantaListVulnerabilityRemediationsResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListVulnerabilityRemediationsTool: ToolConfig< + VantaListVulnerabilityRemediationsParams, + VantaListVulnerabilityRemediationsResponse +> = { + id: 'vanta_list_vulnerability_remediations', + name: 'Vanta List Vulnerability Remediations', + description: + 'List remediated vulnerabilities in a Vanta account with detection, SLA deadline, and remediation dates', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + integrationId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the integration that detected the vulnerability', + }, + severity: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by severity: LOW, MEDIUM, HIGH, or CRITICAL', + }, + isRemediatedOnTime: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by whether the vulnerability was remediated before its SLA deadline', + }, + remediatedAfterDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include remediations completed after this ISO 8601 date', + }, + remediatedBeforeDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include remediations completed before this ISO 8601 date', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_vulnerability_remediations', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + integrationId: params.integrationId, + severity: params.severity, + isRemediatedOnTime: params.isRemediatedOnTime, + remediatedAfterDate: params.remediatedAfterDate, + remediatedBeforeDate: params.remediatedBeforeDate, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta vulnerability remediations' + ), + + outputs: { + remediations: { + type: 'array', + description: 'Vulnerability remediations matching the filters', + items: { type: 'object', properties: VANTA_VULNERABILITY_REMEDIATION_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/list_vulnerable_assets.ts b/apps/sim/tools/vanta/list_vulnerable_assets.ts new file mode 100644 index 0000000000..a09f54a972 --- /dev/null +++ b/apps/sim/tools/vanta/list_vulnerable_assets.ts @@ -0,0 +1,117 @@ +import type { ToolConfig } from '@/tools/types' +import { + VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + VANTA_VULNERABLE_ASSET_OUTPUT_PROPERTIES, +} from '@/tools/vanta/outputs' +import type { + VantaListVulnerableAssetsParams, + VantaListVulnerableAssetsResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaListVulnerableAssetsTool: ToolConfig< + VantaListVulnerableAssetsParams, + VantaListVulnerableAssetsResponse +> = { + id: 'vanta_list_vulnerable_assets', + name: 'Vanta List Vulnerable Assets', + description: + 'List the assets associated with vulnerabilities in a Vanta account (servers, repositories, workstations, and more)', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + q: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search query for vulnerable assets', + }, + integrationId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the integration scanning the asset', + }, + assetType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by asset type: SERVER, SERVERLESS_FUNCTION, CONTAINER, CONTAINER_REPOSITORY, CONTAINER_REPOSITORY_IMAGE, CODE_REPOSITORY, MANIFEST_FILE, WORKSTATION, or OTHER', + }, + assetExternalAccountId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by the external account ID the asset belongs to', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of items per page (1-100, default 10)', + }, + pageCursor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Pagination cursor: pass the endCursor from the previous response to fetch the next page', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_list_vulnerable_assets', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + q: params.q, + integrationId: params.integrationId, + assetType: params.assetType, + assetExternalAccountId: params.assetExternalAccountId, + pageSize: params.pageSize, + pageCursor: params.pageCursor, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to list Vanta vulnerable assets' + ), + + outputs: { + assets: { + type: 'array', + description: 'Vulnerable assets matching the filters', + items: { type: 'object', properties: VANTA_VULNERABLE_ASSET_OUTPUT_PROPERTIES }, + }, + pageInfo: { + type: 'json', + description: + 'Cursor pagination info for the returned page; pass endCursor as pageCursor to fetch the next page', + optional: true, + properties: VANTA_PAGE_INFO_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/outputs.ts b/apps/sim/tools/vanta/outputs.ts new file mode 100644 index 0000000000..e51337f5b1 --- /dev/null +++ b/apps/sim/tools/vanta/outputs.ts @@ -0,0 +1,794 @@ +import type { OutputProperty } from '@/tools/types' + +export const VANTA_PAGE_INFO_OUTPUT_PROPERTIES: Record = { + startCursor: { + type: 'string', + description: 'Cursor pointing to the start of the current page', + optional: true, + }, + endCursor: { + type: 'string', + description: + 'Cursor pointing to the end of the current page; pass as pageCursor to fetch the next page', + optional: true, + }, + hasNextPage: { type: 'boolean', description: 'Whether another page exists after this one' }, + hasPreviousPage: { type: 'boolean', description: 'Whether a page exists before this one' }, +} + +const VANTA_OWNER_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique ID of the owner', optional: true }, + displayName: { type: 'string', description: 'Display name of the owner', optional: true }, + emailAddress: { type: 'string', description: 'Email address of the owner', optional: true }, +} + +const VANTA_CUSTOM_FIELDS_OUTPUT: OutputProperty = { + type: 'array', + description: 'Custom field values configured in the Vanta instance', + items: { + type: 'object', + properties: { + label: { type: 'string', description: 'Custom field label', optional: true }, + value: { + type: 'json', + description: 'Custom field value (string or list of strings)', + optional: true, + }, + }, + }, +} + +export const VANTA_FRAMEWORK_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The framework's unique ID" }, + displayName: { type: 'string', description: "The framework's display name" }, + shorthandName: { type: 'string', description: "The short version of the framework's name" }, + description: { type: 'string', description: "The framework's description" }, + numControlsCompleted: { + type: 'number', + description: 'Number of completed controls in the framework', + }, + numControlsTotal: { type: 'number', description: 'Total number of controls in the framework' }, + numDocumentsPassing: { + type: 'number', + description: 'Number of passing documents in the framework', + }, + numDocumentsTotal: { type: 'number', description: 'Total number of documents in the framework' }, + numTestsPassing: { type: 'number', description: 'Number of passing tests in the framework' }, + numTestsTotal: { type: 'number', description: 'Total number of tests in the framework' }, +} + +export const VANTA_FRAMEWORK_DETAIL_OUTPUT_PROPERTIES: Record = { + ...VANTA_FRAMEWORK_OUTPUT_PROPERTIES, + requirementCategories: { + type: 'array', + description: + "The framework's requirement categories, each with requirements and mapped controls", + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Requirement category ID' }, + name: { type: 'string', description: 'Requirement category name' }, + shorthand: { + type: 'string', + description: 'Requirement category short name', + optional: true, + }, + requirements: { + type: 'array', + description: 'Requirements in this category, each listing its mapped controls', + }, + }, + }, + }, +} + +export const VANTA_CONTROL_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The control's unique ID" }, + externalId: { type: 'string', description: "The control's external ID", optional: true }, + name: { type: 'string', description: "The control's name" }, + description: { type: 'string', description: "The control's description" }, + source: { type: 'string', description: 'The control source, either "Vanta" or "Custom"' }, + domains: { + type: 'array', + description: 'Security domains the control belongs to', + items: { type: 'string' }, + }, + owner: { + type: 'json', + description: "The control's owner", + optional: true, + properties: VANTA_OWNER_OUTPUT_PROPERTIES, + }, + role: { type: 'string', description: "The control's GDPR role, if applicable", optional: true }, + customFields: VANTA_CUSTOM_FIELDS_OUTPUT, + creationDate: { + type: 'string', + description: 'When the control was created (null for Vanta library controls)', + optional: true, + }, + modificationDate: { + type: 'string', + description: 'When the control was last modified (null for Vanta library controls)', + optional: true, + }, +} + +export const VANTA_CONTROL_DETAIL_OUTPUT_PROPERTIES: Record = { + ...VANTA_CONTROL_OUTPUT_PROPERTIES, + note: { type: 'string', description: 'A user-created note for the control', optional: true }, + status: { + type: 'string', + description: 'Control status (NO_EVIDENCE_MAPPED, NOT_STARTED, IN_PROGRESS, or COMPLETED)', + optional: true, + }, + numDocumentsPassing: { + type: 'number', + description: 'Number of passing documents linked to the control', + optional: true, + }, + numDocumentsTotal: { + type: 'number', + description: 'Total number of documents linked to the control', + optional: true, + }, + numTestsPassing: { + type: 'number', + description: 'Number of passing tests linked to the control', + optional: true, + }, + numTestsTotal: { + type: 'number', + description: 'Total number of tests linked to the control', + optional: true, + }, +} + +export const VANTA_TEST_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The test's unique ID" }, + name: { type: 'string', description: "The test's name" }, + description: { type: 'string', description: "The test's description" }, + failureDescription: { type: 'string', description: "The test's failure description" }, + remediationDescription: { type: 'string', description: "The test's remediation description" }, + category: { type: 'string', description: "The test's category" }, + status: { + type: 'string', + description: + 'Test run status (OK, DEACTIVATED, NEEDS_ATTENTION, IN_PROGRESS, INVALID, or NOT_APPLICABLE)', + }, + integrations: { + type: 'array', + description: "The test's third-party integration dependencies", + items: { type: 'string' }, + }, + lastTestRunDate: { type: 'string', description: 'Timestamp of the last test run' }, + latestFlipDate: { + type: 'string', + description: 'Most recent date the test flipped status', + optional: true, + }, + version: { + type: 'json', + description: "The test's version", + optional: true, + properties: { + major: { type: 'number', description: 'Major version number' }, + minor: { type: 'number', description: 'Minor version number' }, + }, + }, + deactivatedStatusInfo: { + type: 'json', + description: "The test's deactivation status", + optional: true, + properties: { + isDeactivated: { type: 'boolean', description: 'Whether the test is deactivated' }, + deactivatedReason: { type: 'string', description: 'Reason for deactivation', optional: true }, + lastUpdatedDate: { + type: 'string', + description: 'Date of the last deactivation status update', + optional: true, + }, + }, + }, + remediationStatusInfo: { + type: 'json', + description: "The test's remediation status", + optional: true, + properties: { + status: { type: 'string', description: 'Remediation status' }, + soonestRemediateByDate: { + type: 'string', + description: 'Soonest remediate-by date', + optional: true, + }, + itemCount: { type: 'number', description: 'Number of items needing remediation' }, + }, + }, + owner: { + type: 'json', + description: "The test's owner", + optional: true, + properties: VANTA_OWNER_OUTPUT_PROPERTIES, + }, +} + +export const VANTA_TEST_ENTITY_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Identifier of the entity' }, + entityStatus: { type: 'string', description: 'Entity status (FAILING or DEACTIVATED)' }, + displayName: { type: 'string', description: 'Display name of the entity' }, + responseType: { type: 'string', description: 'Response type of the entity' }, + deactivatedReason: { type: 'string', description: 'Reason for deactivation', optional: true }, + createdDate: { type: 'string', description: 'Date the entity was first detected' }, + lastUpdatedDate: { type: 'string', description: 'Date of the last update to the entity' }, +} + +export const VANTA_DOCUMENT_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The document's unique ID" }, + title: { type: 'string', description: "The document's title" }, + description: { type: 'string', description: "The document's description" }, + category: { type: 'string', description: "The document's category" }, + ownerId: { type: 'string', description: "User ID of the document's owner", optional: true }, + isSensitive: { type: 'boolean', description: 'Whether the document is sensitive' }, + uploadStatus: { + type: 'string', + description: 'Document status ("Needs document", "Needs update", "Not relevant", or "OK")', + }, + uploadStatusDate: { + type: 'string', + description: 'Date the upload status last changed', + optional: true, + }, + url: { type: 'string', description: 'URL to view the document within Vanta', optional: true }, +} + +export const VANTA_DOCUMENT_DETAIL_OUTPUT_PROPERTIES: Record = { + ...VANTA_DOCUMENT_OUTPUT_PROPERTIES, + note: { type: 'string', description: 'A user note for the document', optional: true }, + nextRenewalDate: { + type: 'string', + description: 'When the document needs to be renewed', + optional: true, + }, + renewalCadence: { + type: 'string', + description: 'How often the document must be renewed', + optional: true, + }, + reminderWindow: { + type: 'string', + description: 'Reminder window ahead of the renewal date (P0D, P1D, P1W, P1M, or P3M)', + optional: true, + }, + subscribers: { + type: 'array', + description: 'Emails subscribed to the document', + items: { type: 'string' }, + }, + deactivatedStatus: { + type: 'json', + description: "The document's deactivation status", + optional: true, + properties: { + isDeactivated: { type: 'boolean', description: 'Whether the document is deactivated' }, + reason: { + type: 'string', + description: 'Reason the document was deactivated', + optional: true, + }, + creationDate: { type: 'string', description: 'Date the document was deactivated' }, + expiration: { type: 'string', description: 'Date the deactivation expires', optional: true }, + }, + }, +} + +export const VANTA_UPLOADED_FILE_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique ID of the uploaded file' }, + fileName: { type: 'string', description: 'File name of the upload', optional: true }, + title: { type: 'string', description: 'Title of the upload' }, + description: { type: 'string', description: 'Description of the upload', optional: true }, + mimeType: { type: 'string', description: 'MIME type of the uploaded file' }, + uploadedBy: { + type: 'json', + description: 'Actor who uploaded the file (a user or an application)', + optional: true, + properties: { + id: { type: 'string', description: 'Actor ID' }, + type: { type: 'string', description: 'Actor type (USER or APPLICATION)' }, + }, + }, + creationDate: { type: 'string', description: 'Date the file was uploaded' }, + updatedDate: { type: 'string', description: 'Date the file was last updated' }, + deletionDate: { + type: 'string', + description: 'Date the file was deleted (null if not deleted)', + optional: true, + }, + effectiveDate: { type: 'string', description: "The file's effective date", optional: true }, + url: { type: 'string', description: "The file's URL" }, +} + +export const VANTA_PERSON_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The person's unique ID" }, + userId: { + type: 'string', + description: 'ID of the associated Vanta user account, if one exists', + optional: true, + }, + emailAddress: { type: 'string', description: "The person's email address" }, + name: { + type: 'json', + description: "The person's name", + optional: true, + properties: { + first: { type: 'string', description: 'First (given) name', optional: true }, + last: { type: 'string', description: 'Last (family) name', optional: true }, + display: { type: 'string', description: 'Display name used in Vanta' }, + }, + }, + employment: { + type: 'json', + description: "The person's employment information", + optional: true, + properties: { + status: { + type: 'string', + description: 'Employment status (UPCOMING, CURRENT, ON_LEAVE, INACTIVE, or FORMER)', + }, + startDate: { type: 'string', description: 'Employment start date' }, + endDate: { type: 'string', description: 'Employment end date, if present', optional: true }, + jobTitle: { type: 'string', description: 'Job title', optional: true }, + }, + }, + leaveInfo: { + type: 'json', + description: "The person's active or upcoming leave, if any", + optional: true, + properties: { + status: { type: 'string', description: 'Leave status (ACTIVE or UPCOMING)' }, + startDate: { type: 'string', description: 'Start of the leave' }, + endDate: { + type: 'string', + description: 'End of the leave (null implies indefinite leave)', + optional: true, + }, + }, + }, + groupIds: { + type: 'array', + description: 'IDs of the groups the person belongs to', + items: { type: 'string' }, + }, + tasksSummary: { + type: 'json', + description: "Aggregated status of the person's tasks", + optional: true, + properties: { + status: { + type: 'string', + description: + 'Overall task status (e.g., NONE, DUE_SOON, OVERDUE, COMPLETE, PAUSED, or an OFFBOARDING_* variant)', + }, + dueDate: { + type: 'string', + description: "Due date of the person's earliest-due task", + optional: true, + }, + completionDate: { + type: 'string', + description: "Date the person's tasks were completed", + optional: true, + }, + }, + }, +} + +export const VANTA_POLICY_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The policy's unique ID" }, + name: { type: 'string', description: "The policy's name" }, + description: { type: 'string', description: "The policy's description" }, + status: { type: 'string', description: 'Policy status (OK or NEEDS_REMEDIATION)' }, + approvedAtDate: { + type: 'string', + description: "The policy's most recent approval date, if applicable", + optional: true, + }, + latestVersionStatus: { + type: 'string', + description: + "Status of the policy's latest version (NOT_STARTED, DRAFT, PENDING_APPROVAL, APPROVED, RENEW_SOON, or EXPIRED)", + }, + latestApprovedVersion: { + type: 'json', + description: 'The latest approved version of the policy, if available', + optional: true, + properties: { + versionId: { type: 'string', description: 'ID of the latest approved version' }, + documents: { + type: 'array', + description: 'Available policy document versions, organized by language', + }, + }, + }, +} + +export const VANTA_VENDOR_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: "The vendor's unique ID" }, + name: { type: 'string', description: "The vendor's display name" }, + status: { type: 'string', description: 'Vendor status (MANAGED, ARCHIVED, or IN_PROCUREMENT)' }, + websiteUrl: { type: 'string', description: "The vendor's website URL", optional: true }, + category: { + type: 'string', + description: "Display name of the vendor's category", + optional: true, + }, + servicesProvided: { + type: 'string', + description: 'Services provided by the vendor', + optional: true, + }, + additionalNotes: { + type: 'string', + description: 'Additional notes about the vendor', + optional: true, + }, + accountManagerName: { + type: 'string', + description: "The vendor's external account manager name", + optional: true, + }, + accountManagerEmail: { + type: 'string', + description: "The vendor's external account manager email", + optional: true, + }, + securityOwnerUserId: { + type: 'string', + description: "Vanta user ID of the vendor's security owner", + optional: true, + }, + businessOwnerUserId: { + type: 'string', + description: "Vanta user ID of the vendor's business owner", + optional: true, + }, + inherentRiskLevel: { + type: 'string', + description: 'Inherent risk level (CRITICAL, HIGH, MEDIUM, LOW, or UNSCORED)', + }, + residualRiskLevel: { + type: 'string', + description: 'Residual risk level (CRITICAL, HIGH, MEDIUM, LOW, or UNSCORED)', + }, + isRiskAutoScored: { + type: 'boolean', + description: "Whether the vendor's risk is automatically scored", + optional: true, + }, + isVisibleToAuditors: { + type: 'boolean', + description: 'Whether auditors can view this vendor', + optional: true, + }, + riskAttributeIds: { + type: 'array', + description: 'Risk attribute IDs assigned to the vendor', + items: { type: 'string' }, + }, + vendorHeadquarters: { + type: 'string', + description: "Country code of the vendor's headquarters", + optional: true, + }, + contractStartDate: { + type: 'string', + description: 'Date the vendor contract began', + optional: true, + }, + contractRenewalDate: { + type: 'string', + description: 'Date the vendor contract is up for renewal', + optional: true, + }, + contractTerminationDate: { + type: 'string', + description: 'Date the vendor contract was terminated', + optional: true, + }, + contractAmount: { + type: 'json', + description: 'Contract amount for the vendor', + optional: true, + properties: { + amount: { type: 'number', description: 'Amount of the contract' }, + currency: { type: 'string', description: 'Currency of the contract' }, + }, + }, + nextSecurityReviewDueDate: { + type: 'string', + description: 'Next due date for a security review', + optional: true, + }, + lastSecurityReviewCompletionDate: { + type: 'string', + description: 'Most recent date a security review was completed', + optional: true, + }, + authDetails: { + type: 'json', + description: "The vendor's authentication details", + optional: true, + properties: { + method: { + type: 'string', + description: 'Authentication method (e.g., SSO, OKTA, USERNAME_PASSWORD)', + optional: true, + }, + passwordMFA: { + type: 'boolean', + description: 'Whether passwords require multi-factor authentication', + optional: true, + }, + passwordMinimumLength: { + type: 'number', + description: 'Minimum password length', + optional: true, + }, + passwordRequiresNumber: { + type: 'boolean', + description: 'Whether passwords require a number', + optional: true, + }, + passwordRequiresSymbol: { + type: 'boolean', + description: 'Whether passwords require a symbol', + optional: true, + }, + }, + }, + customFields: VANTA_CUSTOM_FIELDS_OUTPUT, + latestDecision: { + type: 'json', + description: "The vendor's latest decision (null when no decision has been made)", + optional: true, + properties: { + status: { + type: 'string', + description: 'Decision status (APPROVED, CONDITIONALLY_APPROVED, or NOT_APPROVED)', + }, + lastUpdatedAt: { type: 'string', description: 'When the decision was last updated' }, + }, + }, + linkedTaskTrackerTaskProcurementRequest: { + type: 'json', + description: 'Linked task tracker procurement request, if any', + optional: true, + properties: { + url: { type: 'string', description: 'URL of the procurement request' }, + service: { type: 'string', description: 'Task tracker service' }, + }, + }, +} + +export const VANTA_MONITORED_COMPUTER_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique identifier of the monitored computer' }, + integrationId: { type: 'string', description: 'Integration that reports this computer' }, + lastCheckDate: { + type: 'string', + description: "Date of the computer's most recent report", + optional: true, + }, + screenlock: { + type: 'string', + description: 'Screenlock check outcome (PASS, FAIL, IN_PROGRESS, or NA)', + }, + diskEncryption: { + type: 'string', + description: 'Disk encryption check outcome (PASS, FAIL, IN_PROGRESS, or NA)', + }, + passwordManager: { + type: 'string', + description: 'Password manager check outcome (PASS, FAIL, IN_PROGRESS, or NA)', + }, + antivirusInstallation: { + type: 'string', + description: 'Antivirus check outcome (PASS, FAIL, IN_PROGRESS, or NA)', + }, + operatingSystem: { + type: 'json', + description: "The computer's operating system", + optional: true, + properties: { + type: { type: 'string', description: 'Operating system type (macOS, linux, or windows)' }, + version: { type: 'string', description: 'Operating system version', optional: true }, + }, + }, + owner: { + type: 'json', + description: "The computer's owner", + optional: true, + properties: VANTA_OWNER_OUTPUT_PROPERTIES, + }, + serialNumber: { type: 'string', description: 'Serial number of the computer', optional: true }, + udid: { type: 'string', description: 'Universal device ID of the computer', optional: true }, +} + +export const VANTA_RISK_SCENARIO_OUTPUT_PROPERTIES: Record = { + riskId: { type: 'string', description: 'Unique user-specified ID of the risk scenario' }, + description: { type: 'string', description: 'Description of the risk scenario' }, + likelihood: { + type: 'number', + description: 'Likelihood score (defaults to a 1-5 range; null when unscored)', + optional: true, + }, + impact: { + type: 'number', + description: 'Impact score (defaults to a 1-5 range; null when unscored)', + optional: true, + }, + residualLikelihood: { + type: 'number', + description: 'Residual likelihood score after treatments', + optional: true, + }, + residualImpact: { + type: 'number', + description: 'Residual impact score after treatments', + optional: true, + }, + categories: { + type: 'array', + description: 'Categories this risk scenario belongs to', + items: { type: 'string' }, + }, + ciaCategories: { + type: 'array', + description: 'CIA categories (Confidentiality, Integrity, Availability)', + items: { type: 'string' }, + }, + treatment: { + type: 'string', + description: 'Risk treatment decision (Mitigate, Transfer, Avoid, or Accept)', + optional: true, + }, + owner: { + type: 'string', + description: 'Email of the person responsible for this risk', + optional: true, + }, + note: { + type: 'string', + description: 'Additional context about the risk scenario', + optional: true, + }, + riskRegister: { + type: 'string', + description: 'Name of the associated risk register', + optional: true, + }, + customFields: VANTA_CUSTOM_FIELDS_OUTPUT, + isArchived: { type: 'boolean', description: 'Whether the scenario is archived' }, + reviewStatus: { + type: 'string', + description: + 'Review status (APPROVED, DRAFT, NOT_REVIEWED, AWAITING_SUBMISSION, PENDING_APPROVAL, or REQUESTED_CHANGES)', + }, + requiredApprovers: { + type: 'array', + description: 'Required approvers for this risk scenario', + items: { type: 'string' }, + }, + type: { type: 'string', description: 'Scenario type ("Risk Scenario" or "Enterprise Risk")' }, + identificationDate: { type: 'string', description: 'Date this risk was identified' }, +} + +export const VANTA_VULNERABILITY_REMEDIATION_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique identifier of the remediation' }, + vulnerabilityId: { type: 'string', description: 'ID of the remediated vulnerability' }, + vulnerableAssetId: { type: 'string', description: 'ID of the vulnerable asset' }, + severity: { type: 'string', description: 'Severity of the vulnerability' }, + detectedDate: { + type: 'string', + description: 'Date the vulnerability was first detected', + optional: true, + }, + slaDeadlineDate: { type: 'string', description: 'SLA deadline for remediation', optional: true }, + remediationDate: { + type: 'string', + description: 'Date the vulnerability was remediated', + optional: true, + }, +} + +export const VANTA_VULNERABLE_ASSET_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique identifier of the vulnerable asset' }, + name: { type: 'string', description: 'Display name of the vulnerable asset' }, + assetType: { + type: 'string', + description: + 'Asset type (e.g., SERVER, SERVERLESS_FUNCTION, CONTAINER_REPOSITORY, CODE_REPOSITORY, WORKSTATION)', + }, + hasBeenScanned: { type: 'boolean', description: 'Whether the asset has been scanned' }, + imageScanTag: { + type: 'string', + description: + 'Container image tag that vulnerabilities are retrieved for (container repositories only)', + optional: true, + }, + scanners: { + type: 'array', + description: + 'Integrations scanning this asset, with per-scanner asset details (resource ID, hostnames, IPs, image metadata)', + }, +} + +export const VANTA_VULNERABILITY_OUTPUT_PROPERTIES: Record = { + id: { type: 'string', description: 'Unique identifier of the vulnerability' }, + name: { type: 'string', description: 'Display name of the vulnerability' }, + description: { type: 'string', description: 'Description of the vulnerability' }, + severity: { type: 'string', description: 'Severity (LOW, MEDIUM, HIGH, or CRITICAL)' }, + vulnerabilityType: { + type: 'string', + description: 'Vulnerability type (CONFIGURATION, COMMON, or GROUPED)', + }, + integrationId: { type: 'string', description: 'Integration that scans this vulnerability' }, + targetId: { type: 'string', description: 'ID of the resource the vulnerability was found on' }, + packageIdentifier: { + type: 'string', + description: 'Identifier of the affected package (COMMON and GROUPED vulnerabilities only)', + optional: true, + }, + cvssSeverityScore: { type: 'number', description: 'CVSS severity score', optional: true }, + scannerScore: { type: 'number', description: 'Scanner score', optional: true }, + isFixable: { type: 'boolean', description: 'Whether the vulnerability is fixable' }, + fixedVersion: { + type: 'string', + description: 'Package version that remediates the vulnerability', + optional: true, + }, + remediateByDate: { + type: 'string', + description: 'SLA date by which the vulnerability should be remediated', + optional: true, + }, + firstDetectedDate: { type: 'string', description: 'Date first detected by Vanta' }, + sourceDetectedDate: { + type: 'string', + description: 'Date first detected by the source', + optional: true, + }, + lastDetectedDate: { type: 'string', description: 'Date last detected', optional: true }, + scanSource: { + type: 'string', + description: 'Scanning tool that detected the vulnerability', + optional: true, + }, + externalURL: { type: 'string', description: 'External URL for the vulnerability' }, + relatedVulns: { + type: 'array', + description: 'Related vulnerabilities (GROUPED vulnerabilities only)', + items: { type: 'string' }, + }, + relatedUrls: { + type: 'array', + description: 'Related URLs', + items: { type: 'string' }, + }, + deactivateMetadata: { + type: 'json', + description: 'Deactivation metadata, if the vulnerability was deactivated', + optional: true, + properties: { + isVulnDeactivatedIndefinitely: { + type: 'boolean', + description: 'Whether deactivated indefinitely', + }, + deactivatedUntilDate: { + type: 'string', + description: 'Date the vulnerability will be reactivated', + optional: true, + }, + deactivationReason: { type: 'string', description: 'Reason for deactivation' }, + deactivatedOnDate: { type: 'string', description: 'Date the vulnerability was deactivated' }, + deactivatedBy: { type: 'string', description: 'User who deactivated the vulnerability' }, + }, + }, +} diff --git a/apps/sim/tools/vanta/submit_document.ts b/apps/sim/tools/vanta/submit_document.ts new file mode 100644 index 0000000000..b191db85b0 --- /dev/null +++ b/apps/sim/tools/vanta/submit_document.ts @@ -0,0 +1,63 @@ +import type { ToolConfig } from '@/tools/types' +import type { VantaSubmitDocumentParams, VantaSubmitDocumentResponse } from '@/tools/vanta/types' +import { createVantaTransformResponse, VANTA_QUERY_ROUTE } from '@/tools/vanta/utils' + +export const vantaSubmitDocumentTool: ToolConfig< + VantaSubmitDocumentParams, + VantaSubmitDocumentResponse +> = { + id: 'vanta_submit_document', + name: 'Vanta Submit Document', + description: + 'Submit a Vanta document collection for review so uploaded evidence becomes visible to auditors. Requires credentials with write access.', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + documentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the document to submit', + }, + }, + + request: { + url: VANTA_QUERY_ROUTE, + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + operation: 'vanta_submit_document', + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + documentId: params.documentId, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to submit Vanta document' + ), + + outputs: { + documentId: { type: 'string', description: 'ID of the submitted document' }, + submitted: { type: 'boolean', description: 'Whether the document collection was submitted' }, + }, +} diff --git a/apps/sim/tools/vanta/types.ts b/apps/sim/tools/vanta/types.ts new file mode 100644 index 0000000000..ec1c8165df --- /dev/null +++ b/apps/sim/tools/vanta/types.ts @@ -0,0 +1,779 @@ +import type { ToolResponse } from '@/tools/types' + +export type VantaRegion = 'us' | 'gov' + +export interface VantaBaseParams { + clientId: string + clientSecret: string + region?: VantaRegion +} + +export interface VantaPaginationParams { + pageSize?: number + pageCursor?: string +} + +export interface VantaListFrameworksParams extends VantaBaseParams, VantaPaginationParams {} + +export interface VantaGetFrameworkParams extends VantaBaseParams { + frameworkId: string +} + +export interface VantaListFrameworkControlsParams extends VantaBaseParams, VantaPaginationParams { + frameworkId: string +} + +export interface VantaListControlsParams extends VantaBaseParams, VantaPaginationParams { + frameworkMatchesAny?: string +} + +export interface VantaGetControlParams extends VantaBaseParams { + controlId: string +} + +export interface VantaListControlTestsParams extends VantaBaseParams, VantaPaginationParams { + controlId: string +} + +export interface VantaListControlDocumentsParams extends VantaBaseParams, VantaPaginationParams { + controlId: string +} + +export interface VantaListTestsParams extends VantaBaseParams, VantaPaginationParams { + statusFilter?: string + categoryFilter?: string + frameworkFilter?: string + integrationFilter?: string + controlFilter?: string + ownerFilter?: string + isInRollout?: boolean +} + +export interface VantaGetTestParams extends VantaBaseParams { + testId: string +} + +export interface VantaListTestEntitiesParams extends VantaBaseParams, VantaPaginationParams { + testId: string + entityStatus?: string +} + +export interface VantaListDocumentsParams extends VantaBaseParams, VantaPaginationParams { + frameworkMatchesAny?: string + statusMatchesAny?: string +} + +export interface VantaGetDocumentParams extends VantaBaseParams { + documentId: string +} + +export interface VantaListDocumentUploadsParams extends VantaBaseParams, VantaPaginationParams { + documentId: string +} + +export interface VantaUploadDocumentFileParams extends VantaBaseParams { + documentId: string + file?: unknown + fileContent?: string + fileName?: string + description?: string + effectiveAtDate?: string +} + +export interface VantaDownloadDocumentFileParams extends VantaBaseParams { + documentId: string + uploadedFileId: string +} + +export interface VantaSubmitDocumentParams extends VantaBaseParams { + documentId: string +} + +export interface VantaListPeopleParams extends VantaBaseParams, VantaPaginationParams { + emailAndNameFilter?: string + employmentStatus?: string + groupIdsMatchesAny?: string + tasksSummaryStatusMatchesAny?: string + taskTypeMatchesAny?: string + taskStatusMatchesAny?: string +} + +export interface VantaGetPersonParams extends VantaBaseParams { + personId: string +} + +export interface VantaListPoliciesParams extends VantaBaseParams, VantaPaginationParams {} + +export interface VantaGetPolicyParams extends VantaBaseParams { + policyId: string +} + +export interface VantaListMonitoredComputersParams extends VantaBaseParams, VantaPaginationParams { + complianceStatusFilterMatchesAny?: string +} + +export interface VantaListRiskScenariosParams extends VantaBaseParams, VantaPaginationParams { + searchString?: string + includeIgnored?: boolean + type?: string + ownerMatchesAny?: string + categoryMatchesAny?: string + ciaCategoryMatchesAny?: string + treatmentTypeMatchesAny?: string + inherentScoreGroupMatchesAny?: string + residualScoreGroupMatchesAny?: string + reviewStatusMatchesAny?: string + orderBy?: string +} + +export interface VantaGetRiskScenarioParams extends VantaBaseParams { + riskScenarioId: string +} + +export interface VantaListVulnerabilityRemediationsParams + extends VantaBaseParams, + VantaPaginationParams { + integrationId?: string + severity?: string + isRemediatedOnTime?: boolean + remediatedAfterDate?: string + remediatedBeforeDate?: string +} + +export interface VantaListVulnerableAssetsParams extends VantaBaseParams, VantaPaginationParams { + q?: string + integrationId?: string + assetType?: string + assetExternalAccountId?: string +} + +export interface VantaGetVulnerableAssetParams extends VantaBaseParams { + vulnerableAssetId: string +} + +export interface VantaListVendorsParams extends VantaBaseParams, VantaPaginationParams { + name?: string + statusMatchesAny?: string +} + +export interface VantaGetVendorParams extends VantaBaseParams { + vendorId: string +} + +export interface VantaListVulnerabilitiesParams extends VantaBaseParams, VantaPaginationParams { + q?: string + severity?: string + isFixAvailable?: boolean + isDeactivated?: boolean + includeVulnerabilitiesWithoutSlas?: boolean + packageIdentifier?: string + externalVulnerabilityId?: string + integrationId?: string + vulnerableAssetId?: string + slaDeadlineAfterDate?: string + slaDeadlineBeforeDate?: string +} + +export interface VantaPageInfo { + startCursor: string | null + endCursor: string | null + hasNextPage: boolean + hasPreviousPage: boolean +} + +export interface VantaOwner { + id: string | null + displayName: string | null + emailAddress: string | null +} + +export interface VantaCustomField { + label: string | null + value: string | string[] | null +} + +export interface VantaFramework { + id: string | null + displayName: string | null + shorthandName: string | null + description: string | null + numControlsCompleted: number | null + numControlsTotal: number | null + numDocumentsPassing: number | null + numDocumentsTotal: number | null + numTestsPassing: number | null + numTestsTotal: number | null +} + +export interface VantaFrameworkRequirementControl { + id: string | null + externalId: string | null + name: string | null + description: string | null +} + +export interface VantaFrameworkRequirement { + id: string | null + name: string | null + shorthand: string | null + description: string | null + controls: VantaFrameworkRequirementControl[] +} + +export interface VantaFrameworkRequirementCategory { + id: string | null + name: string | null + shorthand: string | null + requirements: VantaFrameworkRequirement[] +} + +export interface VantaFrameworkDetail extends VantaFramework { + requirementCategories: VantaFrameworkRequirementCategory[] +} + +export interface VantaControl { + id: string | null + externalId: string | null + name: string | null + description: string | null + source: string | null + domains: string[] + owner: VantaOwner | null + role: string | null + customFields: VantaCustomField[] + creationDate: string | null + modificationDate: string | null +} + +export interface VantaControlDetail extends VantaControl { + note: string | null + status: string | null + numDocumentsPassing: number | null + numDocumentsTotal: number | null + numTestsPassing: number | null + numTestsTotal: number | null +} + +export interface VantaTestVersion { + major: number | null + minor: number | null +} + +export interface VantaTestDeactivatedStatusInfo { + isDeactivated: boolean | null + deactivatedReason: string | null + lastUpdatedDate: string | null +} + +export interface VantaTestRemediationStatusInfo { + status: string | null + soonestRemediateByDate: string | null + itemCount: number | null +} + +export interface VantaTest { + id: string | null + name: string | null + description: string | null + failureDescription: string | null + remediationDescription: string | null + category: string | null + status: string | null + integrations: string[] + lastTestRunDate: string | null + latestFlipDate: string | null + version: VantaTestVersion | null + deactivatedStatusInfo: VantaTestDeactivatedStatusInfo | null + remediationStatusInfo: VantaTestRemediationStatusInfo | null + owner: VantaOwner | null +} + +export interface VantaTestEntity { + id: string | null + entityStatus: string | null + displayName: string | null + responseType: string | null + deactivatedReason: string | null + createdDate: string | null + lastUpdatedDate: string | null +} + +export interface VantaDocument { + id: string | null + title: string | null + description: string | null + category: string | null + ownerId: string | null + isSensitive: boolean | null + uploadStatus: string | null + uploadStatusDate: string | null + url: string | null +} + +export interface VantaDocumentDeactivatedStatus { + isDeactivated: boolean | null + reason: string | null + creationDate: string | null + expiration: string | null +} + +export interface VantaDocumentDetail extends VantaDocument { + note: string | null + nextRenewalDate: string | null + renewalCadence: string | null + reminderWindow: string | null + subscribers: string[] + deactivatedStatus: VantaDocumentDeactivatedStatus | null +} + +export interface VantaUploadedByActor { + id: string | null + type: string | null +} + +export interface VantaUploadedFile { + id: string | null + fileName: string | null + title: string | null + description: string | null + mimeType: string | null + uploadedBy: VantaUploadedByActor | null + creationDate: string | null + updatedDate: string | null + deletionDate: string | null + effectiveDate: string | null + url: string | null +} + +export interface VantaPersonName { + first: string | null + last: string | null + display: string | null +} + +export interface VantaPersonEmployment { + status: string | null + startDate: string | null + endDate: string | null + jobTitle: string | null +} + +export interface VantaPersonLeaveInfo { + status: string | null + startDate: string | null + endDate: string | null +} + +export interface VantaPersonTasksSummary { + status: string | null + dueDate: string | null + completionDate: string | null +} + +export interface VantaPerson { + id: string | null + userId: string | null + emailAddress: string | null + name: VantaPersonName | null + employment: VantaPersonEmployment | null + leaveInfo: VantaPersonLeaveInfo | null + groupIds: string[] + tasksSummary: VantaPersonTasksSummary | null +} + +export interface VantaPolicyDocument { + language: string | null + slugId: string | null + url: string | null +} + +export interface VantaPolicyLatestApprovedVersion { + versionId: string | null + documents: VantaPolicyDocument[] +} + +export interface VantaPolicy { + id: string | null + name: string | null + description: string | null + status: string | null + approvedAtDate: string | null + latestVersionStatus: string | null + latestApprovedVersion: VantaPolicyLatestApprovedVersion | null +} + +export interface VantaVendorAuthDetails { + method: string | null + passwordMFA: boolean | null + passwordMinimumLength: number | null + passwordRequiresNumber: boolean | null + passwordRequiresSymbol: boolean | null +} + +export interface VantaVendorContractAmount { + amount: number | null + currency: string | null +} + +export interface VantaVendorDecision { + status: string | null + lastUpdatedAt: string | null +} + +export interface VantaVendorProcurementRequest { + url: string | null + service: string | null +} + +export interface VantaVendor { + id: string | null + name: string | null + status: string | null + websiteUrl: string | null + category: string | null + servicesProvided: string | null + additionalNotes: string | null + accountManagerName: string | null + accountManagerEmail: string | null + securityOwnerUserId: string | null + businessOwnerUserId: string | null + inherentRiskLevel: string | null + residualRiskLevel: string | null + isRiskAutoScored: boolean | null + isVisibleToAuditors: boolean | null + riskAttributeIds: string[] + vendorHeadquarters: string | null + contractStartDate: string | null + contractRenewalDate: string | null + contractTerminationDate: string | null + contractAmount: VantaVendorContractAmount | null + nextSecurityReviewDueDate: string | null + lastSecurityReviewCompletionDate: string | null + authDetails: VantaVendorAuthDetails | null + customFields: VantaCustomField[] + latestDecision: VantaVendorDecision | null + linkedTaskTrackerTaskProcurementRequest: VantaVendorProcurementRequest | null +} + +export interface VantaVulnerabilityDeactivateMetadata { + isVulnDeactivatedIndefinitely: boolean | null + deactivatedUntilDate: string | null + deactivationReason: string | null + deactivatedOnDate: string | null + deactivatedBy: string | null +} + +export interface VantaVulnerability { + id: string | null + name: string | null + description: string | null + severity: string | null + vulnerabilityType: string | null + integrationId: string | null + targetId: string | null + packageIdentifier: string | null + cvssSeverityScore: number | null + scannerScore: number | null + isFixable: boolean | null + fixedVersion: string | null + remediateByDate: string | null + firstDetectedDate: string | null + sourceDetectedDate: string | null + lastDetectedDate: string | null + scanSource: string | null + externalURL: string | null + relatedVulns: string[] + relatedUrls: string[] + deactivateMetadata: VantaVulnerabilityDeactivateMetadata | null +} + +export interface VantaOperatingSystem { + type: string | null + version: string | null +} + +export interface VantaMonitoredComputer { + id: string | null + integrationId: string | null + lastCheckDate: string | null + screenlock: string | null + diskEncryption: string | null + passwordManager: string | null + antivirusInstallation: string | null + operatingSystem: VantaOperatingSystem | null + owner: VantaOwner | null + serialNumber: string | null + udid: string | null +} + +export interface VantaRiskScenario { + riskId: string | null + description: string | null + likelihood: number | null + impact: number | null + residualLikelihood: number | null + residualImpact: number | null + categories: string[] + ciaCategories: string[] + treatment: string | null + owner: string | null + note: string | null + riskRegister: string | null + customFields: VantaCustomField[] + isArchived: boolean | null + reviewStatus: string | null + requiredApprovers: string[] + type: string | null + identificationDate: string | null +} + +export interface VantaVulnerabilityRemediation { + id: string | null + vulnerabilityId: string | null + vulnerableAssetId: string | null + severity: string | null + detectedDate: string | null + slaDeadlineDate: string | null + remediationDate: string | null +} + +export interface VantaAssetTag { + key: string | null + value: string | null +} + +export interface VantaVulnerableAssetScanner { + resourceId: string | null + integrationId: string | null + targetId: string | null + imageDigest: string | null + imagePushedAtDate: string | null + imageTags: string[] + assetTags: VantaAssetTag[] + parentAccountOrOrganization: string | null + biosUuid: string | null + ipv4s: string[] + ipv6s: string[] + macAddresses: string[] + hostnames: string[] + fqdns: string[] + operatingSystems: string[] +} + +export interface VantaVulnerableAsset { + id: string | null + name: string | null + assetType: string | null + hasBeenScanned: boolean | null + imageScanTag: string | null + scanners: VantaVulnerableAssetScanner[] +} + +export interface VantaDownloadedFile { + name: string + mimeType: string + data: string + size: number +} + +export interface VantaListFrameworksResponse extends ToolResponse { + output: { + frameworks: VantaFramework[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetFrameworkResponse extends ToolResponse { + output: { + framework: VantaFrameworkDetail + } +} + +export interface VantaListControlsResponse extends ToolResponse { + output: { + controls: VantaControl[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetControlResponse extends ToolResponse { + output: { + control: VantaControlDetail + } +} + +export interface VantaListTestsResponse extends ToolResponse { + output: { + tests: VantaTest[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetTestResponse extends ToolResponse { + output: { + test: VantaTest + } +} + +export interface VantaListTestEntitiesResponse extends ToolResponse { + output: { + entities: VantaTestEntity[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaListDocumentsResponse extends ToolResponse { + output: { + documents: VantaDocument[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetDocumentResponse extends ToolResponse { + output: { + document: VantaDocumentDetail + } +} + +export interface VantaListDocumentUploadsResponse extends ToolResponse { + output: { + uploads: VantaUploadedFile[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaUploadDocumentFileResponse extends ToolResponse { + output: { + upload: VantaUploadedFile + } +} + +export interface VantaDownloadDocumentFileResponse extends ToolResponse { + output: { + file: VantaDownloadedFile + name: string + mimeType: string + size: number + } +} + +export interface VantaSubmitDocumentResponse extends ToolResponse { + output: { + documentId: string + submitted: boolean + } +} + +export interface VantaGetPersonResponse extends ToolResponse { + output: { + person: VantaPerson + } +} + +export interface VantaGetPolicyResponse extends ToolResponse { + output: { + policy: VantaPolicy + } +} + +export interface VantaListMonitoredComputersResponse extends ToolResponse { + output: { + computers: VantaMonitoredComputer[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaListRiskScenariosResponse extends ToolResponse { + output: { + riskScenarios: VantaRiskScenario[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetRiskScenarioResponse extends ToolResponse { + output: { + riskScenario: VantaRiskScenario + } +} + +export interface VantaListVulnerabilityRemediationsResponse extends ToolResponse { + output: { + remediations: VantaVulnerabilityRemediation[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaListVulnerableAssetsResponse extends ToolResponse { + output: { + assets: VantaVulnerableAsset[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetVulnerableAssetResponse extends ToolResponse { + output: { + asset: VantaVulnerableAsset + } +} + +export interface VantaListPeopleResponse extends ToolResponse { + output: { + people: VantaPerson[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaListPoliciesResponse extends ToolResponse { + output: { + policies: VantaPolicy[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaListVendorsResponse extends ToolResponse { + output: { + vendors: VantaVendor[] + pageInfo: VantaPageInfo | null + } +} + +export interface VantaGetVendorResponse extends ToolResponse { + output: { + vendor: VantaVendor + } +} + +export interface VantaListVulnerabilitiesResponse extends ToolResponse { + output: { + vulnerabilities: VantaVulnerability[] + pageInfo: VantaPageInfo | null + } +} + +export type VantaResponse = + | VantaListFrameworksResponse + | VantaGetFrameworkResponse + | VantaListControlsResponse + | VantaGetControlResponse + | VantaListTestsResponse + | VantaGetTestResponse + | VantaListTestEntitiesResponse + | VantaListDocumentsResponse + | VantaGetDocumentResponse + | VantaListDocumentUploadsResponse + | VantaUploadDocumentFileResponse + | VantaDownloadDocumentFileResponse + | VantaSubmitDocumentResponse + | VantaListPeopleResponse + | VantaGetPersonResponse + | VantaListPoliciesResponse + | VantaGetPolicyResponse + | VantaListVendorsResponse + | VantaGetVendorResponse + | VantaListMonitoredComputersResponse + | VantaListVulnerabilitiesResponse + | VantaListVulnerabilityRemediationsResponse + | VantaListVulnerableAssetsResponse + | VantaGetVulnerableAssetResponse + | VantaListRiskScenariosResponse + | VantaGetRiskScenarioResponse diff --git a/apps/sim/tools/vanta/upload_document_file.ts b/apps/sim/tools/vanta/upload_document_file.ts new file mode 100644 index 0000000000..fa1aba7229 --- /dev/null +++ b/apps/sim/tools/vanta/upload_document_file.ts @@ -0,0 +1,104 @@ +import type { ToolConfig } from '@/tools/types' +import { VANTA_UPLOADED_FILE_OUTPUT_PROPERTIES } from '@/tools/vanta/outputs' +import type { + VantaUploadDocumentFileParams, + VantaUploadDocumentFileResponse, +} from '@/tools/vanta/types' +import { createVantaTransformResponse } from '@/tools/vanta/utils' + +export const vantaUploadDocumentFileTool: ToolConfig< + VantaUploadDocumentFileParams, + VantaUploadDocumentFileResponse +> = { + id: 'vanta_upload_document_file', + name: 'Vanta Upload Document File', + description: + 'Upload an evidence file to a Vanta document. Requires credentials with the vanta-api.documents:upload scope.', + version: '1.0.0', + + params: { + clientId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client ID', + }, + clientSecret: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vanta OAuth application client secret', + }, + region: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Vanta API region: "us" (api.vanta.com, default) or "gov" (api.vanta-gov.com)', + }, + documentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Unique ID of the document to attach the file to', + }, + file: { + type: 'file', + required: false, + visibility: 'user-or-llm', + description: 'The evidence file to upload', + }, + fileContent: { + type: 'string', + required: false, + visibility: 'hidden', + description: 'Base64-encoded file content (alternative to file)', + }, + fileName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional file name override', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the uploaded evidence (e.g., "Q3 access review evidence")', + }, + effectiveAtDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'ISO 8601 date indicating when the document is effective from', + }, + }, + + request: { + url: '/api/tools/vanta/upload', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + documentId: params.documentId, + file: params.file, + fileContent: params.fileContent, + fileName: params.fileName, + description: params.description, + effectiveAtDate: params.effectiveAtDate, + }), + }, + + transformResponse: createVantaTransformResponse( + 'Failed to upload file to Vanta document' + ), + + outputs: { + upload: { + type: 'json', + description: 'Metadata of the uploaded file', + properties: VANTA_UPLOADED_FILE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/vanta/utils.ts b/apps/sim/tools/vanta/utils.ts new file mode 100644 index 0000000000..b4522903ee --- /dev/null +++ b/apps/sim/tools/vanta/utils.ts @@ -0,0 +1,698 @@ +import type { + VantaControl, + VantaControlDetail, + VantaCustomField, + VantaDocument, + VantaDocumentDetail, + VantaFramework, + VantaFrameworkDetail, + VantaFrameworkRequirement, + VantaFrameworkRequirementCategory, + VantaFrameworkRequirementControl, + VantaMonitoredComputer, + VantaOwner, + VantaPageInfo, + VantaPerson, + VantaPolicy, + VantaPolicyDocument, + VantaRegion, + VantaRiskScenario, + VantaTest, + VantaTestEntity, + VantaUploadedFile, + VantaVendor, + VantaVulnerability, + VantaVulnerabilityRemediation, + VantaVulnerableAsset, + VantaVulnerableAssetScanner, +} from '@/tools/vanta/types' + +export const VANTA_API_BASE_URLS: Record = { + us: 'https://api.vanta.com', + gov: 'https://api.vanta-gov.com', +} + +/** Read-only scope used by every query and download operation. */ +export const VANTA_READ_SCOPE = 'vanta-api.all:read' + +/** + * Scope string for document evidence uploads, taken verbatim from Vanta's + * "Upload a document" guide (the upload scope cannot be requested alone). + */ +export const VANTA_DOCUMENT_UPLOAD_SCOPE = + 'vanta-api.all:read vanta-api.all:write vanta-api.documents:upload' + +export function getVantaBaseUrl(region: VantaRegion | undefined): string { + return VANTA_API_BASE_URLS[region ?? 'us'] +} + +export const VANTA_QUERY_ROUTE = '/api/tools/vanta/query' + +/** + * Builds the standard transformResponse for Vanta tools, which call internal + * API routes that return `{ success, output }` JSON or `{ success: false, + * error }` on failure. + */ +export function createVantaTransformResponse( + fallbackError: string +) { + return async (response: Response): Promise => { + const data = await response.json() + if (!response.ok || data.success === false) { + throw new Error(data.error || fallbackError) + } + return { success: true, output: data.output } as R + } +} + +type JsonRecord = Record + +function isJsonRecord(value: unknown): value is JsonRecord { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +/** + * Coerces an unknown single-resource response body to a record so the + * normalizers can run on it; non-object bodies normalize to all-null fields. + */ +export function asVantaRecord(value: unknown): JsonRecord { + return isJsonRecord(value) ? value : {} +} + +function getString(value: unknown): string | null { + return typeof value === 'string' ? value : null +} + +function getNumber(value: unknown): number | null { + return typeof value === 'number' && Number.isFinite(value) ? value : null +} + +function getBoolean(value: unknown): boolean | null { + return typeof value === 'boolean' ? value : null +} + +function getStringArray(value: unknown): string[] { + if (!Array.isArray(value)) return [] + return value.filter((entry): entry is string => typeof entry === 'string') +} + +function getRecordArray(value: unknown): JsonRecord[] { + if (!Array.isArray(value)) return [] + return value.filter(isJsonRecord) +} + +/** + * Extracts a human-readable error message from a Vanta API error body. + */ +export function extractVantaError(data: unknown, fallback: string): string { + if (!isJsonRecord(data)) return fallback + + if (isJsonRecord(data.error)) { + const nested = getString(data.error.message) ?? getString(data.error.code) + if (nested) return nested + } + + return ( + getString(data.message) ?? + getString(data.error_description) ?? + getString(data.error) ?? + fallback + ) +} + +/** + * Exchanges Vanta OAuth client credentials for a bearer token. Vanta tokens + * live for one hour and only one token per application is active at a time, + * so a fresh token is requested per API call and used immediately. + */ +export async function getVantaAccessToken(params: { + clientId: string + clientSecret: string + region?: VantaRegion + scope: string +}): Promise { + const response = await fetch(`${getVantaBaseUrl(params.region)}/oauth/token`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + client_id: params.clientId, + client_secret: params.clientSecret, + scope: params.scope, + grant_type: 'client_credentials', + }), + cache: 'no-store', + }) + + const data: unknown = await response.json().catch(() => null) + if (!response.ok) { + throw new Error(extractVantaError(data, 'Failed to authenticate with Vanta')) + } + + if (!isJsonRecord(data) || typeof data.access_token !== 'string') { + throw new Error('Vanta authentication did not return an access token') + } + + return data.access_token +} + +/** + * Builds a Vanta v1 API URL, appending only query parameters that have a + * value. Array values are appended as repeated parameters. + */ +export function buildVantaUrl( + baseUrl: string, + path: string, + query?: Record +): string { + const url = new URL(`${baseUrl}/v1${path}`) + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value == null || value === '') continue + if (Array.isArray(value)) { + for (const entry of value) { + url.searchParams.append(key, entry) + } + } else { + url.searchParams.set(key, String(value)) + } + } + } + return url.toString() +} + +/** + * Splits a comma-separated filter value into trimmed entries, returning + * undefined when no usable entries remain. + */ +export function splitVantaCommaList(value: string | null | undefined): string[] | undefined { + if (!value) return undefined + const entries = value + .split(',') + .map((entry) => entry.trim()) + .filter(Boolean) + return entries.length > 0 ? entries : undefined +} + +/** + * Unwraps the `{ results: { data, pageInfo } }` envelope that every Vanta + * list endpoint returns. + */ +export function getVantaListResults(data: unknown): { + data: JsonRecord[] + pageInfo: VantaPageInfo | null +} { + if (!isJsonRecord(data) || !isJsonRecord(data.results)) { + return { data: [], pageInfo: null } + } + return { + data: getRecordArray(data.results.data), + pageInfo: normalizeVantaPageInfo(data.results.pageInfo), + } +} + +export function normalizeVantaPageInfo(value: unknown): VantaPageInfo | null { + if (!isJsonRecord(value)) return null + return { + startCursor: getString(value.startCursor), + endCursor: getString(value.endCursor), + hasNextPage: getBoolean(value.hasNextPage) ?? false, + hasPreviousPage: getBoolean(value.hasPreviousPage) ?? false, + } +} + +function normalizeVantaOwner(value: unknown): VantaOwner | null { + if (!isJsonRecord(value)) return null + return { + id: getString(value.id), + displayName: getString(value.displayName), + emailAddress: getString(value.emailAddress), + } +} + +function normalizeVantaCustomFields(value: unknown): VantaCustomField[] { + return getRecordArray(value).map((field) => ({ + label: getString(field.label), + value: Array.isArray(field.value) ? getStringArray(field.value) : getString(field.value), + })) +} + +export function normalizeVantaFramework(resource: JsonRecord): VantaFramework { + return { + id: getString(resource.id), + displayName: getString(resource.displayName), + shorthandName: getString(resource.shorthandName), + description: getString(resource.description), + numControlsCompleted: getNumber(resource.numControlsCompleted), + numControlsTotal: getNumber(resource.numControlsTotal), + numDocumentsPassing: getNumber(resource.numDocumentsPassing), + numDocumentsTotal: getNumber(resource.numDocumentsTotal), + numTestsPassing: getNumber(resource.numTestsPassing), + numTestsTotal: getNumber(resource.numTestsTotal), + } +} + +function normalizeVantaFrameworkRequirementControl( + resource: JsonRecord +): VantaFrameworkRequirementControl { + return { + id: getString(resource.id), + externalId: getString(resource.externalId), + name: getString(resource.name), + description: getString(resource.description), + } +} + +function normalizeVantaFrameworkRequirement(resource: JsonRecord): VantaFrameworkRequirement { + return { + id: getString(resource.id), + name: getString(resource.name), + shorthand: getString(resource.shorthand), + description: getString(resource.description), + controls: getRecordArray(resource.controls).map(normalizeVantaFrameworkRequirementControl), + } +} + +function normalizeVantaFrameworkRequirementCategory( + resource: JsonRecord +): VantaFrameworkRequirementCategory { + return { + id: getString(resource.id), + name: getString(resource.name), + shorthand: getString(resource.shorthand), + requirements: getRecordArray(resource.requirements).map(normalizeVantaFrameworkRequirement), + } +} + +export function normalizeVantaFrameworkDetail(resource: JsonRecord): VantaFrameworkDetail { + return { + ...normalizeVantaFramework(resource), + requirementCategories: getRecordArray(resource.requirementCategories).map( + normalizeVantaFrameworkRequirementCategory + ), + } +} + +export function normalizeVantaControl(resource: JsonRecord): VantaControl { + return { + id: getString(resource.id), + externalId: getString(resource.externalId), + name: getString(resource.name), + description: getString(resource.description), + source: getString(resource.source), + domains: getStringArray(resource.domains), + owner: normalizeVantaOwner(resource.owner), + role: getString(resource.role), + customFields: normalizeVantaCustomFields(resource.customFields), + creationDate: getString(resource.creationDate), + modificationDate: getString(resource.modificationDate), + } +} + +export function normalizeVantaControlDetail(resource: JsonRecord): VantaControlDetail { + return { + ...normalizeVantaControl(resource), + note: getString(resource.note), + status: getString(resource.status), + numDocumentsPassing: getNumber(resource.numDocumentsPassing), + numDocumentsTotal: getNumber(resource.numDocumentsTotal), + numTestsPassing: getNumber(resource.numTestsPassing), + numTestsTotal: getNumber(resource.numTestsTotal), + } +} + +export function normalizeVantaTest(resource: JsonRecord): VantaTest { + const version = isJsonRecord(resource.version) + ? { major: getNumber(resource.version.major), minor: getNumber(resource.version.minor) } + : null + const deactivatedStatusInfo = isJsonRecord(resource.deactivatedStatusInfo) + ? { + isDeactivated: getBoolean(resource.deactivatedStatusInfo.isDeactivated), + deactivatedReason: getString(resource.deactivatedStatusInfo.deactivatedReason), + lastUpdatedDate: getString(resource.deactivatedStatusInfo.lastUpdatedDate), + } + : null + const remediationStatusInfo = isJsonRecord(resource.remediationStatusInfo) + ? { + status: getString(resource.remediationStatusInfo.status), + soonestRemediateByDate: getString(resource.remediationStatusInfo.soonestRemediateByDate), + itemCount: getNumber(resource.remediationStatusInfo.itemCount), + } + : null + + return { + id: getString(resource.id), + name: getString(resource.name), + description: getString(resource.description), + failureDescription: getString(resource.failureDescription), + remediationDescription: getString(resource.remediationDescription), + category: getString(resource.category), + status: getString(resource.status), + integrations: getStringArray(resource.integrations), + lastTestRunDate: getString(resource.lastTestRunDate), + latestFlipDate: getString(resource.latestFlipDate), + version, + deactivatedStatusInfo, + remediationStatusInfo, + owner: normalizeVantaOwner(resource.owner), + } +} + +export function normalizeVantaTestEntity(resource: JsonRecord): VantaTestEntity { + return { + id: getString(resource.id), + entityStatus: getString(resource.entityStatus), + displayName: getString(resource.displayName), + responseType: getString(resource.responseType), + deactivatedReason: getString(resource.deactivatedReason), + createdDate: getString(resource.createdDate), + lastUpdatedDate: getString(resource.lastUpdatedDate), + } +} + +export function normalizeVantaDocument(resource: JsonRecord): VantaDocument { + return { + id: getString(resource.id), + title: getString(resource.title), + description: getString(resource.description), + category: getString(resource.category), + ownerId: getString(resource.ownerId), + isSensitive: getBoolean(resource.isSensitive), + uploadStatus: getString(resource.uploadStatus), + uploadStatusDate: getString(resource.uploadStatusDate), + url: getString(resource.url), + } +} + +export function normalizeVantaDocumentDetail(resource: JsonRecord): VantaDocumentDetail { + const deactivatedStatus = isJsonRecord(resource.deactivatedStatus) + ? { + isDeactivated: getBoolean(resource.deactivatedStatus.isDeactivated), + reason: getString(resource.deactivatedStatus.reason), + creationDate: getString(resource.deactivatedStatus.creationDate), + expiration: getString(resource.deactivatedStatus.expiration), + } + : null + + return { + ...normalizeVantaDocument(resource), + note: getString(resource.note), + nextRenewalDate: getString(resource.nextRenewalDate), + renewalCadence: getString(resource.renewalCadence), + reminderWindow: getString(resource.reminderWindow), + subscribers: getStringArray(resource.subscribers), + deactivatedStatus, + } +} + +export function normalizeVantaUploadedFile(resource: JsonRecord): VantaUploadedFile { + const uploadedBy = isJsonRecord(resource.uploadedBy) + ? { id: getString(resource.uploadedBy.id), type: getString(resource.uploadedBy.type) } + : null + + return { + id: getString(resource.id), + fileName: getString(resource.fileName), + title: getString(resource.title), + description: getString(resource.description), + mimeType: getString(resource.mimeType), + uploadedBy, + creationDate: getString(resource.creationDate), + updatedDate: getString(resource.updatedDate), + deletionDate: getString(resource.deletionDate), + effectiveDate: getString(resource.effectiveDate), + url: getString(resource.url), + } +} + +export function normalizeVantaPerson(resource: JsonRecord): VantaPerson { + const name = isJsonRecord(resource.name) + ? { + first: getString(resource.name.first), + last: getString(resource.name.last), + display: getString(resource.name.display), + } + : null + const employment = isJsonRecord(resource.employment) + ? { + status: getString(resource.employment.status), + startDate: getString(resource.employment.startDate), + endDate: getString(resource.employment.endDate), + jobTitle: getString(resource.employment.jobTitle), + } + : null + const leaveInfo = isJsonRecord(resource.leaveInfo) + ? { + status: getString(resource.leaveInfo.status), + startDate: getString(resource.leaveInfo.startDate), + endDate: getString(resource.leaveInfo.endDate), + } + : null + const tasksSummary = isJsonRecord(resource.tasksSummary) + ? { + status: getString(resource.tasksSummary.status), + dueDate: getString(resource.tasksSummary.dueDate), + completionDate: getString(resource.tasksSummary.completionDate), + } + : null + + return { + id: getString(resource.id), + userId: getString(resource.userId), + emailAddress: getString(resource.emailAddress), + name, + employment, + leaveInfo, + groupIds: getStringArray(resource.groupIds), + tasksSummary, + } +} + +function normalizeVantaPolicyDocument(resource: JsonRecord): VantaPolicyDocument { + return { + language: getString(resource.language), + slugId: getString(resource.slugId), + url: getString(resource.url), + } +} + +export function normalizeVantaPolicy(resource: JsonRecord): VantaPolicy { + const latestApprovedVersion = isJsonRecord(resource.latestApprovedVersion) + ? { + versionId: getString(resource.latestApprovedVersion.versionId), + documents: getRecordArray(resource.latestApprovedVersion.documents).map( + normalizeVantaPolicyDocument + ), + } + : null + + return { + id: getString(resource.id), + name: getString(resource.name), + description: getString(resource.description), + status: getString(resource.status), + approvedAtDate: getString(resource.approvedAtDate), + latestVersionStatus: isJsonRecord(resource.latestVersion) + ? getString(resource.latestVersion.status) + : null, + latestApprovedVersion, + } +} + +export function normalizeVantaVendor(resource: JsonRecord): VantaVendor { + const authDetails = isJsonRecord(resource.authDetails) + ? { + method: getString(resource.authDetails.method), + passwordMFA: getBoolean(resource.authDetails.passwordMFA), + passwordMinimumLength: getNumber(resource.authDetails.passwordMinimumLength), + passwordRequiresNumber: getBoolean(resource.authDetails.passwordRequiresNumber), + passwordRequiresSymbol: getBoolean(resource.authDetails.passwordRequiresSymbol), + } + : null + const contractAmount = isJsonRecord(resource.contractAmount) + ? { + amount: getNumber(resource.contractAmount.amount), + currency: getString(resource.contractAmount.currency), + } + : null + const latestDecision = isJsonRecord(resource.latestDecision) + ? { + status: getString(resource.latestDecision.status), + lastUpdatedAt: getString(resource.latestDecision.lastUpdatedAt), + } + : null + const procurementRequest = isJsonRecord(resource.linkedTaskTrackerTaskProcurementRequest) + ? { + url: getString(resource.linkedTaskTrackerTaskProcurementRequest.url), + service: getString(resource.linkedTaskTrackerTaskProcurementRequest.service), + } + : null + + return { + id: getString(resource.id), + name: getString(resource.name), + status: getString(resource.status), + websiteUrl: getString(resource.websiteUrl), + category: isJsonRecord(resource.category) ? getString(resource.category.displayName) : null, + servicesProvided: getString(resource.servicesProvided), + additionalNotes: getString(resource.additionalNotes), + accountManagerName: getString(resource.accountManagerName), + accountManagerEmail: getString(resource.accountManagerEmail), + securityOwnerUserId: getString(resource.securityOwnerUserId), + businessOwnerUserId: getString(resource.businessOwnerUserId), + inherentRiskLevel: getString(resource.inherentRiskLevel), + residualRiskLevel: getString(resource.residualRiskLevel), + isRiskAutoScored: getBoolean(resource.isRiskAutoScored), + isVisibleToAuditors: getBoolean(resource.isVisibleToAuditors), + riskAttributeIds: getStringArray(resource.riskAttributeIds), + vendorHeadquarters: getString(resource.vendorHeadquarters), + contractStartDate: getString(resource.contractStartDate), + contractRenewalDate: getString(resource.contractRenewalDate), + contractTerminationDate: getString(resource.contractTerminationDate), + contractAmount, + nextSecurityReviewDueDate: getString(resource.nextSecurityReviewDueDate), + lastSecurityReviewCompletionDate: getString(resource.lastSecurityReviewCompletionDate), + authDetails, + customFields: normalizeVantaCustomFields(resource.customFields), + latestDecision, + linkedTaskTrackerTaskProcurementRequest: procurementRequest, + } +} + +function getComputerStatusOutcome(value: unknown): string | null { + return isJsonRecord(value) ? getString(value.outcome) : null +} + +export function normalizeVantaMonitoredComputer(resource: JsonRecord): VantaMonitoredComputer { + const operatingSystem = isJsonRecord(resource.operatingSystem) + ? { + type: getString(resource.operatingSystem.type), + version: getString(resource.operatingSystem.version), + } + : null + + return { + id: getString(resource.id), + integrationId: getString(resource.integrationId), + lastCheckDate: getString(resource.lastCheckDate), + screenlock: getComputerStatusOutcome(resource.screenlock), + diskEncryption: getComputerStatusOutcome(resource.diskEncryption), + passwordManager: getComputerStatusOutcome(resource.passwordManager), + antivirusInstallation: getComputerStatusOutcome(resource.antivirusInstallation), + operatingSystem, + owner: normalizeVantaOwner(resource.owner), + serialNumber: getString(resource.serialNumber), + udid: getString(resource.udid), + } +} + +export function normalizeVantaRiskScenario(resource: JsonRecord): VantaRiskScenario { + return { + riskId: getString(resource.riskId), + description: getString(resource.description), + likelihood: getNumber(resource.likelihood), + impact: getNumber(resource.impact), + residualLikelihood: getNumber(resource.residualLikelihood), + residualImpact: getNumber(resource.residualImpact), + categories: getStringArray(resource.categories), + ciaCategories: getStringArray(resource.ciaCategories), + treatment: getString(resource.treatment), + owner: getString(resource.owner), + note: getString(resource.note), + riskRegister: getString(resource.riskRegister), + customFields: normalizeVantaCustomFields(resource.customFields), + isArchived: getBoolean(resource.isArchived), + reviewStatus: getString(resource.reviewStatus), + requiredApprovers: getStringArray(resource.requiredApprovers), + type: getString(resource.type), + identificationDate: getString(resource.identificationDate), + } +} + +export function normalizeVantaVulnerabilityRemediation( + resource: JsonRecord +): VantaVulnerabilityRemediation { + return { + id: getString(resource.id), + vulnerabilityId: getString(resource.vulnerabilityId), + vulnerableAssetId: getString(resource.vulnerableAssetId), + severity: getString(resource.severity), + detectedDate: getString(resource.detectedDate), + slaDeadlineDate: getString(resource.slaDeadlineDate), + remediationDate: getString(resource.remediationDate), + } +} + +function normalizeVantaVulnerableAssetScanner(resource: JsonRecord): VantaVulnerableAssetScanner { + return { + resourceId: getString(resource.resourceId), + integrationId: getString(resource.integrationId), + targetId: getString(resource.targetId), + imageDigest: getString(resource.imageDigest), + imagePushedAtDate: getString(resource.imagePushedAtDate), + imageTags: getStringArray(resource.imageTags), + assetTags: getRecordArray(resource.assetTags).map((tag) => ({ + key: getString(tag.key), + value: getString(tag.value), + })), + parentAccountOrOrganization: getString(resource.parentAccountOrOrganization), + biosUuid: getString(resource.biosUuid), + ipv4s: getStringArray(resource.ipv4s), + ipv6s: getStringArray(resource.ipv6s), + macAddresses: getStringArray(resource.macAddresses), + hostnames: getStringArray(resource.hostnames), + fqdns: getStringArray(resource.fqdns), + operatingSystems: getStringArray(resource.operatingSystems), + } +} + +export function normalizeVantaVulnerableAsset(resource: JsonRecord): VantaVulnerableAsset { + return { + id: getString(resource.id), + name: getString(resource.name), + assetType: getString(resource.assetType), + hasBeenScanned: getBoolean(resource.hasBeenScanned), + imageScanTag: getString(resource.imageScanTag), + scanners: getRecordArray(resource.scanners).map(normalizeVantaVulnerableAssetScanner), + } +} + +export function normalizeVantaVulnerability(resource: JsonRecord): VantaVulnerability { + const deactivateMetadata = isJsonRecord(resource.deactivateMetadata) + ? { + isVulnDeactivatedIndefinitely: getBoolean( + resource.deactivateMetadata.isVulnDeactivatedIndefinitely + ), + deactivatedUntilDate: getString(resource.deactivateMetadata.deactivatedUntilDate), + deactivationReason: getString(resource.deactivateMetadata.deactivationReason), + deactivatedOnDate: getString(resource.deactivateMetadata.deactivatedOnDate), + deactivatedBy: getString(resource.deactivateMetadata.deactivatedBy), + } + : null + + return { + id: getString(resource.id), + name: getString(resource.name), + description: getString(resource.description), + severity: getString(resource.severity), + vulnerabilityType: getString(resource.vulnerabilityType), + integrationId: getString(resource.integrationId), + targetId: getString(resource.targetId), + packageIdentifier: getString(resource.packageIdentifier), + cvssSeverityScore: getNumber(resource.cvssSeverityScore), + scannerScore: getNumber(resource.scannerScore), + isFixable: getBoolean(resource.isFixable), + fixedVersion: getString(resource.fixedVersion), + remediateByDate: getString(resource.remediateByDate), + firstDetectedDate: getString(resource.firstDetectedDate), + sourceDetectedDate: getString(resource.sourceDetectedDate), + lastDetectedDate: getString(resource.lastDetectedDate), + scanSource: getString(resource.scanSource), + externalURL: getString(resource.externalURL), + relatedVulns: getStringArray(resource.relatedVulns), + relatedUrls: getStringArray(resource.relatedUrls), + deactivateMetadata, + } +} diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 645b41e592..0f611a51e0 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 812, - zodRoutes: 812, + totalRoutes: 815, + zodRoutes: 815, nonZodRoutes: 0, } as const From 57e53c18f2ef92bca593fb874b6b98d046f91940 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 22:15:32 -0700 Subject: [PATCH 2/8] fix(integrations): use write-only scope for vanta document submit and stream-cap document downloads --- .../sim/app/api/tools/vanta/download/route.ts | 36 +++++++++++++++++-- apps/sim/app/api/tools/vanta/query/route.ts | 4 +-- apps/sim/tools/vanta/utils.ts | 3 ++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/apps/sim/app/api/tools/vanta/download/route.ts b/apps/sim/app/api/tools/vanta/download/route.ts index 83198e2220..ed3153b2ce 100644 --- a/apps/sim/app/api/tools/vanta/download/route.ts +++ b/apps/sim/app/api/tools/vanta/download/route.ts @@ -28,6 +28,33 @@ function downloadSizeError(bytes: number): NextResponse { ) } +/** + * Reads a response body incrementally, aborting as soon as the accumulated + * size exceeds the limit so oversized files are never fully buffered. + * Returns null when the limit is exceeded. + */ +async function readBodyWithLimit(response: Response, maxBytes: number): Promise { + const reader = response.body?.getReader() + if (!reader) { + const buffer = Buffer.from(await response.arrayBuffer()) + return buffer.length > maxBytes ? null : buffer + } + + const chunks: Uint8Array[] = [] + let total = 0 + while (true) { + const { done, value } = await reader.read() + if (done) break + total += value.byteLength + if (total > maxBytes) { + await reader.cancel() + return null + } + chunks.push(value) + } + return Buffer.concat(chunks) +} + /** * Extracts the filename from a Content-Disposition header, if present. */ @@ -99,9 +126,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return downloadSizeError(contentLength) } - const buffer = Buffer.from(await response.arrayBuffer()) - if (buffer.length > MAX_DOWNLOAD_SIZE_BYTES) { - return downloadSizeError(buffer.length) + const buffer = await readBodyWithLimit(response, MAX_DOWNLOAD_SIZE_BYTES) + if (buffer === null) { + return NextResponse.json( + { success: false, error: 'File exceeds download limit of 100MB' }, + { status: 400 } + ) } const mimeType = response.headers.get('content-type') || 'application/octet-stream' diff --git a/apps/sim/app/api/tools/vanta/query/route.ts b/apps/sim/app/api/tools/vanta/query/route.ts index 6cd0bb7c58..12d0e9e441 100644 --- a/apps/sim/app/api/tools/vanta/query/route.ts +++ b/apps/sim/app/api/tools/vanta/query/route.ts @@ -32,8 +32,8 @@ import { normalizeVantaVulnerabilityRemediation, normalizeVantaVulnerableAsset, splitVantaCommaList, - VANTA_DOCUMENT_UPLOAD_SCOPE, VANTA_READ_SCOPE, + VANTA_WRITE_SCOPE, } from '@/tools/vanta/utils' export const dynamic = 'force-dynamic' @@ -382,7 +382,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const baseUrl = getVantaBaseUrl(params.region) const scope = - params.operation === 'vanta_submit_document' ? VANTA_DOCUMENT_UPLOAD_SCOPE : VANTA_READ_SCOPE + params.operation === 'vanta_submit_document' ? VANTA_WRITE_SCOPE : VANTA_READ_SCOPE const accessToken = await getVantaAccessToken({ clientId: params.clientId, clientSecret: params.clientSecret, diff --git a/apps/sim/tools/vanta/utils.ts b/apps/sim/tools/vanta/utils.ts index b4522903ee..bed98a9e61 100644 --- a/apps/sim/tools/vanta/utils.ts +++ b/apps/sim/tools/vanta/utils.ts @@ -35,6 +35,9 @@ export const VANTA_API_BASE_URLS: Record = { /** Read-only scope used by every query and download operation. */ export const VANTA_READ_SCOPE = 'vanta-api.all:read' +/** Read-write scope used by write operations that do not upload files. */ +export const VANTA_WRITE_SCOPE = 'vanta-api.all:read vanta-api.all:write' + /** * Scope string for document evidence uploads, taken verbatim from Vanta's * "Upload a document" guide (the upload scope cannot be requested alone). From 26ba1469309b26eb372db3e6a14b215b8080f266 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 22:24:30 -0700 Subject: [PATCH 3/8] fix(integrations): vanta review feedback - unique svg ids, mime type for base64 uploads, auth check consistency --- apps/docs/components/icons.tsx | 11 ++++++---- .../content/docs/en/integrations/vanta.mdx | 1 + .../sim/app/api/tools/vanta/download/route.ts | 22 +++++++++---------- apps/sim/app/api/tools/vanta/query/route.ts | 22 +++++++++---------- apps/sim/app/api/tools/vanta/upload/route.ts | 4 ++-- apps/sim/components/icons.tsx | 11 ++++++---- apps/sim/lib/api/contracts/tools/vanta.ts | 1 + apps/sim/tools/vanta/types.ts | 1 + apps/sim/tools/vanta/upload_document_file.ts | 8 +++++++ 9 files changed, 49 insertions(+), 32 deletions(-) diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 573eb40256..074255de2a 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -7556,12 +7556,15 @@ export function OnePasswordIcon(props: SVGProps) { } export function VantaIcon(props: SVGProps) { + const id = useId() + const clipId = `vanta_clip_${id}` + const maskId = `vanta_mask_${id}` return ( - + ) { > - + ) { - + diff --git a/apps/docs/content/docs/en/integrations/vanta.mdx b/apps/docs/content/docs/en/integrations/vanta.mdx index 154dc7dfd4..2086268cc4 100644 --- a/apps/docs/content/docs/en/integrations/vanta.mdx +++ b/apps/docs/content/docs/en/integrations/vanta.mdx @@ -332,6 +332,7 @@ Upload an evidence file to a Vanta document. Requires credentials with the vanta | `file` | file | No | The evidence file to upload | | `fileContent` | string | No | Base64-encoded file content \(alternative to file\) | | `fileName` | string | No | Optional file name override | +| `mimeType` | string | No | MIME type of the file \(e.g., application/pdf\); used when uploading base64 content, since uploaded files already carry their own type | | `description` | string | No | Description of the uploaded evidence \(e.g., "Q3 access review evidence"\) | | `effectiveAtDate` | string | No | ISO 8601 date indicating when the document is effective from | diff --git a/apps/sim/app/api/tools/vanta/download/route.ts b/apps/sim/app/api/tools/vanta/download/route.ts index ed3153b2ce..cae3798f0c 100644 --- a/apps/sim/app/api/tools/vanta/download/route.ts +++ b/apps/sim/app/api/tools/vanta/download/route.ts @@ -75,18 +75,18 @@ function getFileNameFromContentDisposition(header: string | null): string | null export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() - const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) - if (!authResult.success) { - logger.warn(`[${requestId}] Unauthorized Vanta download attempt`, { - error: authResult.error || 'Unauthorized', - }) - return NextResponse.json( - { success: false, error: authResult.error || 'Unauthorized' }, - { status: 401 } - ) - } - try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Vanta download attempt`, { + error: authResult.error || 'Unauthorized', + }) + return NextResponse.json( + { success: false, error: authResult.error || 'Unauthorized' }, + { status: 401 } + ) + } + const parsed = await parseRequest(vantaDownloadContract, request, {}) if (!parsed.success) return parsed.response const params = parsed.data.body diff --git a/apps/sim/app/api/tools/vanta/query/route.ts b/apps/sim/app/api/tools/vanta/query/route.ts index 12d0e9e441..4707f4030f 100644 --- a/apps/sim/app/api/tools/vanta/query/route.ts +++ b/apps/sim/app/api/tools/vanta/query/route.ts @@ -364,18 +364,18 @@ function buildVantaOutput(params: VantaQueryBody, data: unknown): Record { const requestId = generateRequestId() - const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) - if (!authResult.success) { - logger.warn(`[${requestId}] Unauthorized Vanta query attempt`, { - error: authResult.error || 'Unauthorized', - }) - return NextResponse.json( - { success: false, error: authResult.error || 'Unauthorized' }, - { status: 401 } - ) - } - try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Vanta query attempt`, { + error: authResult.error || 'Unauthorized', + }) + return NextResponse.json( + { success: false, error: authResult.error || 'Unauthorized' }, + { status: 401 } + ) + } + const parsed = await parseRequest(vantaQueryContract, request, {}) if (!parsed.success) return parsed.response const params = parsed.data.body diff --git a/apps/sim/app/api/tools/vanta/upload/route.ts b/apps/sim/app/api/tools/vanta/upload/route.ts index 5e526eb9fb..4e4cbc6f2f 100644 --- a/apps/sim/app/api/tools/vanta/upload/route.ts +++ b/apps/sim/app/api/tools/vanta/upload/route.ts @@ -72,11 +72,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) fileName = params.fileName || userFile.name - mimeType = userFile.type || 'application/octet-stream' + mimeType = userFile.type || params.mimeType || 'application/octet-stream' } else if (params.fileContent) { fileBuffer = Buffer.from(params.fileContent, 'base64') fileName = params.fileName || 'file' - mimeType = 'application/octet-stream' + mimeType = params.mimeType || 'application/octet-stream' } else { return NextResponse.json({ success: false, error: 'File is required' }, { status: 400 }) } diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 573eb40256..074255de2a 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -7556,12 +7556,15 @@ export function OnePasswordIcon(props: SVGProps) { } export function VantaIcon(props: SVGProps) { + const id = useId() + const clipId = `vanta_clip_${id}` + const maskId = `vanta_mask_${id}` return ( - + ) { > - + ) { - + diff --git a/apps/sim/lib/api/contracts/tools/vanta.ts b/apps/sim/lib/api/contracts/tools/vanta.ts index 1578c67d03..bd4c8793ae 100644 --- a/apps/sim/lib/api/contracts/tools/vanta.ts +++ b/apps/sim/lib/api/contracts/tools/vanta.ts @@ -678,6 +678,7 @@ export const vantaUploadBodySchema = vantaBaseBodySchema.extend({ file: FileInputSchema.optional().nullable(), fileContent: z.string().nullish(), fileName: z.string().nullish(), + mimeType: z.string().nullish(), description: z.string().nullish(), effectiveAtDate: z.string().nullish(), }) diff --git a/apps/sim/tools/vanta/types.ts b/apps/sim/tools/vanta/types.ts index ec1c8165df..e59804a3e6 100644 --- a/apps/sim/tools/vanta/types.ts +++ b/apps/sim/tools/vanta/types.ts @@ -76,6 +76,7 @@ export interface VantaUploadDocumentFileParams extends VantaBaseParams { file?: unknown fileContent?: string fileName?: string + mimeType?: string description?: string effectiveAtDate?: string } diff --git a/apps/sim/tools/vanta/upload_document_file.ts b/apps/sim/tools/vanta/upload_document_file.ts index fa1aba7229..6e1b1da9fd 100644 --- a/apps/sim/tools/vanta/upload_document_file.ts +++ b/apps/sim/tools/vanta/upload_document_file.ts @@ -59,6 +59,13 @@ export const vantaUploadDocumentFileTool: ToolConfig< visibility: 'user-or-llm', description: 'Optional file name override', }, + mimeType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'MIME type of the file (e.g., application/pdf); used when uploading base64 content, since uploaded files already carry their own type', + }, description: { type: 'string', required: false, @@ -85,6 +92,7 @@ export const vantaUploadDocumentFileTool: ToolConfig< file: params.file, fileContent: params.fileContent, fileName: params.fileName, + mimeType: params.mimeType, description: params.description, effectiveAtDate: params.effectiveAtDate, }), From 9576c47d2491f08bcf25a99e830a249ea301e544 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 22:25:40 -0700 Subject: [PATCH 4/8] fix(integrations): bound vanta base64 fileContent size at the contract level --- apps/sim/lib/api/contracts/tools/vanta.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/api/contracts/tools/vanta.ts b/apps/sim/lib/api/contracts/tools/vanta.ts index bd4c8793ae..ecae5ebb34 100644 --- a/apps/sim/lib/api/contracts/tools/vanta.ts +++ b/apps/sim/lib/api/contracts/tools/vanta.ts @@ -676,7 +676,10 @@ export const vantaQueryContract = defineRouteContract({ export const vantaUploadBodySchema = vantaBaseBodySchema.extend({ documentId: requiredId('Document ID'), file: FileInputSchema.optional().nullable(), - fileContent: z.string().nullish(), + fileContent: z + .string() + .max(140 * 1024 * 1024, 'fileContent exceeds the 100MB upload limit') + .nullish(), fileName: z.string().nullish(), mimeType: z.string().nullish(), description: z.string().nullish(), From 68ad957a681d5e04a5f34306032f5ae35a12e742 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 22:35:14 -0700 Subject: [PATCH 5/8] improvement(integrations): cache vanta tokens and retry once on revocation to avoid concurrent token races --- .../sim/app/api/tools/vanta/download/route.ts | 28 +++---- apps/sim/app/api/tools/vanta/query/route.ts | 31 ++++---- apps/sim/app/api/tools/vanta/upload/route.ts | 52 +++++++------ apps/sim/tools/vanta/utils.ts | 76 +++++++++++++++++-- 4 files changed, 130 insertions(+), 57 deletions(-) diff --git a/apps/sim/app/api/tools/vanta/download/route.ts b/apps/sim/app/api/tools/vanta/download/route.ts index cae3798f0c..48a23ac94a 100644 --- a/apps/sim/app/api/tools/vanta/download/route.ts +++ b/apps/sim/app/api/tools/vanta/download/route.ts @@ -9,7 +9,7 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { buildVantaUrl, extractVantaError, - getVantaAccessToken, + fetchVantaWithAuth, getVantaBaseUrl, VANTA_READ_SCOPE, } from '@/tools/vanta/utils' @@ -91,13 +91,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const params = parsed.data.body - const accessToken = await getVantaAccessToken({ - clientId: params.clientId, - clientSecret: params.clientSecret, - region: params.region, - scope: VANTA_READ_SCOPE, - }) - const mediaUrl = buildVantaUrl( getVantaBaseUrl(params.region), `/documents/${encodeURIComponent(params.documentId)}/uploads/${encodeURIComponent(params.uploadedFileId)}/media` @@ -108,11 +101,20 @@ export const POST = withRouteHandler(async (request: NextRequest) => { uploadedFileId: params.uploadedFileId, }) - const response = await fetch(mediaUrl, { - method: 'GET', - headers: { Authorization: `Bearer ${accessToken}` }, - cache: 'no-store', - }) + const response = await fetchVantaWithAuth( + { + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope: VANTA_READ_SCOPE, + }, + (accessToken) => + fetch(mediaUrl, { + method: 'GET', + headers: { Authorization: `Bearer ${accessToken}` }, + cache: 'no-store', + }) + ) if (!response.ok) { const errorData: unknown = await response.json().catch(() => null) diff --git a/apps/sim/app/api/tools/vanta/query/route.ts b/apps/sim/app/api/tools/vanta/query/route.ts index 4707f4030f..102c20af00 100644 --- a/apps/sim/app/api/tools/vanta/query/route.ts +++ b/apps/sim/app/api/tools/vanta/query/route.ts @@ -11,7 +11,7 @@ import { asVantaRecord, buildVantaUrl, extractVantaError, - getVantaAccessToken, + fetchVantaWithAuth, getVantaBaseUrl, getVantaListResults, normalizeVantaControl, @@ -383,24 +383,27 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const baseUrl = getVantaBaseUrl(params.region) const scope = params.operation === 'vanta_submit_document' ? VANTA_WRITE_SCOPE : VANTA_READ_SCOPE - const accessToken = await getVantaAccessToken({ - clientId: params.clientId, - clientSecret: params.clientSecret, - region: params.region, - scope, - }) logger.info(`[${requestId}] Vanta query request`, { operation: params.operation }) const apiRequest = buildVantaApiRequest(baseUrl, params) - const response = await fetch(apiRequest.url, { - method: apiRequest.method, - headers: { - Accept: 'application/json', - Authorization: `Bearer ${accessToken}`, + const response = await fetchVantaWithAuth( + { + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope, }, - cache: 'no-store', - }) + (accessToken) => + fetch(apiRequest.url, { + method: apiRequest.method, + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + cache: 'no-store', + }) + ) if (!response.ok) { const errorData: unknown = await response.json().catch(() => null) diff --git a/apps/sim/app/api/tools/vanta/upload/route.ts b/apps/sim/app/api/tools/vanta/upload/route.ts index 4e4cbc6f2f..a76cc848c0 100644 --- a/apps/sim/app/api/tools/vanta/upload/route.ts +++ b/apps/sim/app/api/tools/vanta/upload/route.ts @@ -13,7 +13,7 @@ import { asVantaRecord, buildVantaUrl, extractVantaError, - getVantaAccessToken, + fetchVantaWithAuth, getVantaBaseUrl, normalizeVantaUploadedFile, VANTA_DOCUMENT_UPLOAD_SCOPE, @@ -85,38 +85,44 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return uploadSizeError(fileBuffer.length) } - const accessToken = await getVantaAccessToken({ - clientId: params.clientId, - clientSecret: params.clientSecret, - region: params.region, - scope: VANTA_DOCUMENT_UPLOAD_SCOPE, - }) - logger.info(`[${requestId}] Uploading file to Vanta document`, { documentId: params.documentId, fileName, size: fileBuffer.length, }) - const formData = new FormData() - formData.append('file', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), fileName) - if (params.description) { - formData.append('description', params.description) - } - if (params.effectiveAtDate) { - formData.append('effectiveAtDate', params.effectiveAtDate) - } - const uploadUrl = buildVantaUrl( getVantaBaseUrl(params.region), `/documents/${encodeURIComponent(params.documentId)}/uploads` ) - const response = await fetch(uploadUrl, { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}` }, - body: formData, - cache: 'no-store', - }) + const response = await fetchVantaWithAuth( + { + clientId: params.clientId, + clientSecret: params.clientSecret, + region: params.region, + scope: VANTA_DOCUMENT_UPLOAD_SCOPE, + }, + (accessToken) => { + const formData = new FormData() + formData.append( + 'file', + new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), + fileName + ) + if (params.description) { + formData.append('description', params.description) + } + if (params.effectiveAtDate) { + formData.append('effectiveAtDate', params.effectiveAtDate) + } + return fetch(uploadUrl, { + method: 'POST', + headers: { Authorization: `Bearer ${accessToken}` }, + body: formData, + cache: 'no-store', + }) + } + ) const data: unknown = await response.json().catch(() => null) if (!response.ok) { diff --git a/apps/sim/tools/vanta/utils.ts b/apps/sim/tools/vanta/utils.ts index bed98a9e61..2ce25c1ac4 100644 --- a/apps/sim/tools/vanta/utils.ts +++ b/apps/sim/tools/vanta/utils.ts @@ -123,17 +123,52 @@ export function extractVantaError(data: unknown, fallback: string): string { ) } -/** - * Exchanges Vanta OAuth client credentials for a bearer token. Vanta tokens - * live for one hour and only one token per application is active at a time, - * so a fresh token is requested per API call and used immediately. - */ -export async function getVantaAccessToken(params: { +export interface VantaTokenParams { clientId: string clientSecret: string region?: VantaRegion scope: string -}): Promise { +} + +interface VantaCachedToken { + token: string + expiresAt: number +} + +/** + * In-memory token cache. Vanta only keeps one access token active per + * application — requesting a new token revokes the previous one — so reusing + * a cached token keeps concurrent tool executions with the same credentials + * from revoking each other's tokens mid-flight. + */ +const vantaTokenCache = new Map() + +/** Evict cached tokens well before their one-hour expiry. */ +const VANTA_TOKEN_EXPIRY_BUFFER_MS = 10 * 60 * 1000 + +function vantaTokenCacheKey(params: VantaTokenParams): string { + return [params.region ?? 'us', params.scope, params.clientId, params.clientSecret].join('|') +} + +/** + * Returns a bearer token for the Vanta API, exchanging OAuth client + * credentials and caching the result until shortly before expiry. Pass + * `forceRefresh` when a cached token has been revoked (e.g., by another + * process exchanging the same credentials). + */ +export async function getVantaAccessToken( + params: VantaTokenParams, + options?: { forceRefresh?: boolean } +): Promise { + const cacheKey = vantaTokenCacheKey(params) + if (!options?.forceRefresh) { + const cached = vantaTokenCache.get(cacheKey) + if (cached && cached.expiresAt > Date.now()) { + return cached.token + } + } + vantaTokenCache.delete(cacheKey) + const response = await fetch(`${getVantaBaseUrl(params.region)}/oauth/token`, { method: 'POST', headers: { @@ -158,9 +193,36 @@ export async function getVantaAccessToken(params: { throw new Error('Vanta authentication did not return an access token') } + const expiresInMs = (getNumber(data.expires_in) ?? 0) * 1000 + if (expiresInMs > VANTA_TOKEN_EXPIRY_BUFFER_MS) { + vantaTokenCache.set(cacheKey, { + token: data.access_token, + expiresAt: Date.now() + expiresInMs - VANTA_TOKEN_EXPIRY_BUFFER_MS, + }) + } + return data.access_token } +/** + * Performs an authenticated Vanta API request. When the request comes back + * 401 — typically because another process exchanged the same credentials and + * revoked the cached token — it retries once with a freshly exchanged token. + */ +export async function fetchVantaWithAuth( + tokenParams: VantaTokenParams, + doFetch: (accessToken: string) => Promise +): Promise { + const accessToken = await getVantaAccessToken(tokenParams) + const response = await doFetch(accessToken) + if (response.status !== 401) { + return response + } + + const freshToken = await getVantaAccessToken(tokenParams, { forceRefresh: true }) + return doFetch(freshToken) +} + /** * Builds a Vanta v1 API URL, appending only query parameters that have a * value. Array values are appended as repeated parameters. From 6519dac864a8e3a617386f937aea89a710809215 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 23:03:19 -0700 Subject: [PATCH 6/8] improvement(integrations): deduplicate in-flight vanta token exchanges --- apps/sim/tools/vanta/utils.ts | 62 ++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/apps/sim/tools/vanta/utils.ts b/apps/sim/tools/vanta/utils.ts index 2ce25c1ac4..863da8e03c 100644 --- a/apps/sim/tools/vanta/utils.ts +++ b/apps/sim/tools/vanta/utils.ts @@ -143,6 +143,13 @@ interface VantaCachedToken { */ const vantaTokenCache = new Map() +/** + * In-flight token exchanges, keyed like the cache. Concurrent callers that + * miss the cache join the same exchange instead of issuing competing ones + * that would revoke each other's tokens. + */ +const vantaTokenExchanges = new Map>() + /** Evict cached tokens well before their one-hour expiry. */ const VANTA_TOKEN_EXPIRY_BUFFER_MS = 10 * 60 * 1000 @@ -150,25 +157,7 @@ function vantaTokenCacheKey(params: VantaTokenParams): string { return [params.region ?? 'us', params.scope, params.clientId, params.clientSecret].join('|') } -/** - * Returns a bearer token for the Vanta API, exchanging OAuth client - * credentials and caching the result until shortly before expiry. Pass - * `forceRefresh` when a cached token has been revoked (e.g., by another - * process exchanging the same credentials). - */ -export async function getVantaAccessToken( - params: VantaTokenParams, - options?: { forceRefresh?: boolean } -): Promise { - const cacheKey = vantaTokenCacheKey(params) - if (!options?.forceRefresh) { - const cached = vantaTokenCache.get(cacheKey) - if (cached && cached.expiresAt > Date.now()) { - return cached.token - } - } - vantaTokenCache.delete(cacheKey) - +async function exchangeVantaToken(params: VantaTokenParams, cacheKey: string): Promise { const response = await fetch(`${getVantaBaseUrl(params.region)}/oauth/token`, { method: 'POST', headers: { @@ -204,6 +193,41 @@ export async function getVantaAccessToken( return data.access_token } +/** + * Returns a bearer token for the Vanta API, exchanging OAuth client + * credentials and caching the result until shortly before expiry. Concurrent + * callers share a single in-flight exchange. Pass `forceRefresh` when a + * cached token has been revoked (e.g., by another process exchanging the + * same credentials); a force refresh still joins any exchange already in + * flight, since that exchange yields an equally fresh token. + */ +export async function getVantaAccessToken( + params: VantaTokenParams, + options?: { forceRefresh?: boolean } +): Promise { + const cacheKey = vantaTokenCacheKey(params) + if (!options?.forceRefresh) { + const cached = vantaTokenCache.get(cacheKey) + if (cached && cached.expiresAt > Date.now()) { + return cached.token + } + } + + const inFlight = vantaTokenExchanges.get(cacheKey) + if (inFlight) { + return inFlight + } + + vantaTokenCache.delete(cacheKey) + const exchange = exchangeVantaToken(params, cacheKey) + vantaTokenExchanges.set(cacheKey, exchange) + try { + return await exchange + } finally { + vantaTokenExchanges.delete(cacheKey) + } +} + /** * Performs an authenticated Vanta API request. When the request comes back * 401 — typically because another process exchanged the same credentials and From 4e3d257f125af6d07b10e080775be0aa44985e4d Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 23:14:22 -0700 Subject: [PATCH 7/8] fix(integrations): hash vanta client secret in token cache keys --- apps/sim/tools/vanta/utils.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/sim/tools/vanta/utils.ts b/apps/sim/tools/vanta/utils.ts index 863da8e03c..356ff7b32d 100644 --- a/apps/sim/tools/vanta/utils.ts +++ b/apps/sim/tools/vanta/utils.ts @@ -153,8 +153,20 @@ const vantaTokenExchanges = new Map>() /** Evict cached tokens well before their one-hour expiry. */ const VANTA_TOKEN_EXPIRY_BUFFER_MS = 10 * 60 * 1000 -function vantaTokenCacheKey(params: VantaTokenParams): string { - return [params.region ?? 'us', params.scope, params.clientId, params.clientSecret].join('|') +/** + * Derives the cache key for a credential set. The client secret is included + * only as a SHA-256 digest so plaintext secrets never persist in the + * long-lived cache maps. + */ +async function vantaTokenCacheKey(params: VantaTokenParams): Promise { + const digest = await crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(`${params.clientId}:${params.clientSecret}`) + ) + const secretHash = Array.from(new Uint8Array(digest)) + .map((byte) => byte.toString(16).padStart(2, '0')) + .join('') + return [params.region ?? 'us', params.scope, params.clientId, secretHash].join('|') } async function exchangeVantaToken(params: VantaTokenParams, cacheKey: string): Promise { @@ -205,7 +217,7 @@ export async function getVantaAccessToken( params: VantaTokenParams, options?: { forceRefresh?: boolean } ): Promise { - const cacheKey = vantaTokenCacheKey(params) + const cacheKey = await vantaTokenCacheKey(params) if (!options?.forceRefresh) { const cached = vantaTokenCache.get(cacheKey) if (cached && cached.expiresAt > Date.now()) { From c16087823ab8374ccbed4de7a89e9bfb5ca2ca7e Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 11 Jun 2026 23:29:59 -0700 Subject: [PATCH 8/8] improvement(integrations): expose vanta upload mime type in block and align fileContent bound with the 100MB cap --- apps/sim/blocks/blocks/vanta.ts | 13 +++++++++++++ apps/sim/lib/api/contracts/tools/vanta.ts | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/vanta.ts b/apps/sim/blocks/blocks/vanta.ts index d85f7bd69d..8b52fd1928 100644 --- a/apps/sim/blocks/blocks/vanta.ts +++ b/apps/sim/blocks/blocks/vanta.ts @@ -184,6 +184,14 @@ export const VantaBlock: BlockConfig = { condition: { field: 'operation', value: 'upload_document_file' }, mode: 'advanced', }, + { + id: 'uploadMimeType', + title: 'MIME Type', + type: 'short-input', + placeholder: 'e.g., application/pdf (used when the file has no type of its own)', + condition: { field: 'operation', value: 'upload_document_file' }, + mode: 'advanced', + }, { id: 'uploadDescription', title: 'Description', @@ -820,6 +828,7 @@ export const VantaBlock: BlockConfig = { const normalizedFile = normalizeFileInput(rest.file, { single: true }) if (normalizedFile) result.file = normalizedFile result.fileName = optionalString(rest.uploadFileName) + result.mimeType = optionalString(rest.uploadMimeType) result.description = optionalString(rest.uploadDescription) result.effectiveAtDate = optionalString(rest.effectiveAtDate) break @@ -882,6 +891,10 @@ export const VantaBlock: BlockConfig = { uploadedFileId: { type: 'string', description: 'Uploaded file ID' }, file: { type: 'json', description: 'Evidence file to upload' }, uploadFileName: { type: 'string', description: 'Optional file name override' }, + uploadMimeType: { + type: 'string', + description: 'MIME type override used when the uploaded content has no type of its own', + }, uploadDescription: { type: 'string', description: 'Description of the uploaded evidence' }, effectiveAtDate: { type: 'string', description: 'Effective date of the document (ISO 8601)' }, frameworkMatchesAny: { type: 'string', description: 'Comma-separated framework ID filters' }, diff --git a/apps/sim/lib/api/contracts/tools/vanta.ts b/apps/sim/lib/api/contracts/tools/vanta.ts index ecae5ebb34..22ccc18864 100644 --- a/apps/sim/lib/api/contracts/tools/vanta.ts +++ b/apps/sim/lib/api/contracts/tools/vanta.ts @@ -673,12 +673,16 @@ export const vantaQueryContract = defineRouteContract({ response: { mode: 'json', schema: vantaQueryResponseSchema }, }) +const VANTA_MAX_UPLOAD_BYTES = 100 * 1024 * 1024 +/** Base64 length of the largest allowed upload (4 chars per 3 bytes). */ +const VANTA_MAX_UPLOAD_BASE64_LENGTH = Math.ceil(VANTA_MAX_UPLOAD_BYTES / 3) * 4 + export const vantaUploadBodySchema = vantaBaseBodySchema.extend({ documentId: requiredId('Document ID'), file: FileInputSchema.optional().nullable(), fileContent: z .string() - .max(140 * 1024 * 1024, 'fileContent exceeds the 100MB upload limit') + .max(VANTA_MAX_UPLOAD_BASE64_LENGTH, 'fileContent exceeds the 100MB upload limit') .nullish(), fileName: z.string().nullish(), mimeType: z.string().nullish(),