---
title: "Connectors to GRC, privacy, vulnerability and ticket tools"
description: "Push assure evidence to your GRC tool, run privacy requests from Transcend or OneTrust, send SBOM and VEX to your vulnerability tool, and open one ticket per failure."
section: "interfaces"
status: "Building for 1.2 · ships as a beta API"
spec: "connectors"
canonical: "https://mainsdk.dev/docs/interfaces/connectors"
updated: "2026-10-28"
---

# Connectors to GRC, privacy, vulnerability and ticket tools

> Push assure evidence to your GRC tool, run privacy requests from Transcend or OneTrust, send SBOM and VEX to your vulnerability tool, and open one ticket per failure.

Status: Building for 1.2 · ships as a beta API · Canonical: https://mainsdk.dev/docs/interfaces/connectors · Updated: 2026-10-28

`connectors` sends MainSDK's evidence and findings to the tools your security and privacy teams already use: GRC, privacy requests, SBOM and VEX, and tickets.

A connector is a driver behind one contract, `connector@1`. It reads its credential from `secrets`, writes one audit line per call, and is never in the request path.

> **Note:** Beta. Import it from `mainsdk/connectors` and pass the network, secrets and log as ports. The `mainsdk assure` engine that produces the runs is not built yet, so `--push` and `--tickets` act on a run you supply. Vendor endpoints are written from their public documentation and verified against mocks, not live tools.

## At a glance

- Status: Building for 1.2 · ships as a beta API
- Phase: 3 (Mobile and regulated)
- Milestone: 1.2 Regulated industries
- Plan: free in your app (Apache-2.0)
- Spec: https://github.com/hubxero/mainsdk/blob/develop/docs/specs/connectors.md
- Category: Security and privacy

Calls:

- `connectorFor({ kind, settings }, ports)`
- `connectorsFrom(config, ports)`
- `pushAssure(connectors, run, options)`
- `failuresOf(run, { owner, now })`
- `syncTickets(tickets, run, options)`
- `pushRelease(vulns, release, options)`
- `importFindings(vulns, options)`
- `processPrivacy(privacy, { steps, sign }, options)`
- `testConnectors(connectors, options)`

In main.yaml:

```yaml
connectors:
  grc:     { driver: servicenow-irm, instance: https://acme.service-now.com, auth: secret:SN_TOKEN }
  privacy: { driver: transcend, auth: secret:TRANSCEND_KEY }
  vulns:   { driver: dependency-track, url: https://dt.acme.internal, auth: secret:DT_KEY }
  tickets: { driver: github }
```

Guarantees, whatever the driver:

- Credentials come from secrets and a literal token fails the check
- Every connector call writes one audit line with no credential in it
- A connector is never in the request path
- A privacy request is complete only after every driver confirms


## Drivers

Pick one in main.yaml with `connectors.driver`.

| Driver | Id in main.yaml | Default | Note |
| --- | --- | --- | --- |
| ServiceNow IRM | `servicenow-irm` |  |  |
| Archer | `archer` |  |  |
| OneTrust GRC | `onetrust-grc` |  |  |
| Transcend | `transcend` |  |  |
| OneTrust privacy requests | `onetrust-privacy` |  |  |
| Dependency-Track | `dependency-track` |  |  |
| Snyk | `snyk` |  |  |
| Wiz | `wiz` |  |  |
| DefectDojo | `defectdojo` |  |  |
| GitHub Issues | `github` |  |  |
| Jira | `jira` |  |  |
| Local mock (tests and mainsdk dev) | `local` |  |  |

## Codes

| Code | Where | Meaning |
| --- | --- | --- |
| `MAIN-CONNECTOR-0001` | check | A connector's settings are not usable: an unknown driver, a missing https `instance` or `url`, or `auth` that is not a `secret:NAME` reference |
| `MAIN-CONNECTOR-0002` | runtime |  |

## Features

| Feature | Phase | Milestone | Spec ticket |
| --- | --- | --- | --- |
| connector@1 contract and conformance suite | 3 | 1.2 | `connectors#connector-contract` |
| GRC connectors | 3 | 1.2 | `connectors#grc-connectors` |
| Privacy-request connectors | 3 | 1.2 | `connectors#privacy-connectors` |
| Vulnerability-tool connectors | 3 | 1.2 | `connectors#vuln-connectors` |
| Ticket connectors | 3 | 1.2 | `connectors#ticket-connectors` |
| Secrets and audit for every call | 3 | 1.2 | `connectors#connector-auth` |

## MainSDK Cloud

Everything on this page is free in your app.


## Quick start

```yaml title="main.yaml"
connectors:
  grc:     { driver: servicenow-irm, instance: https://acme.service-now.com, auth: secret:SN_TOKEN }
  privacy: { driver: transcend, auth: secret:TRANSCEND_KEY }
  vulns:   { driver: dependency-track, url: https://dt.acme.internal, auth: secret:DT_KEY }
  tickets: { driver: github }
```

```ts title="app/connectors.ts"
import { app } from "mainsdk";
import {
  connectorsFrom,
  httpOver,
  pushAssure,
  type AssureRun,
  type ConnectorsConfig,
} from "mainsdk/connectors";

const a = app();
export const connectors = connectorsFrom(a.config.connectors as ConnectorsConfig, {
  http: httpOver(fetch),
  secrets: (name) => a.secrets.get(name),
  log: (entry) => a.log.info("connector call", { ...entry }),
});

export async function push(run: AssureRun) {
  if (connectors.grc) await pushAssure(connectors.grc, run); // one control result per failure, evidence attached
}
```

```text
mainsdk connectors test      # dry run against each configured connector
```

## Configuration

Each of `grc`, `privacy`, `vulns` and `tickets` takes the settings of its driver.

| Key | Meaning |
| --- | --- |
| `driver` | One of the drivers below |
| `instance` or `url` | The tool's https address, not needed for `github` |
| `auth` | `secret:NAME`; a literal token fails the check |
| `repo`, `project`, `org` | The repository (`github`), project (`jira`) or organisation (`snyk`) |

## What you get

| Call | What it does |
| --- | --- |
| `connectorFor({ kind, settings }, ports)` | Build one connector, or fail with `MAIN-CONNECTOR-0001` |
| `connectorsFrom(config, ports)` | Build every connector in the `connectors:` section |
| `pushAssure(connector, run, options)` | Send one control result per control, keyed by the pinned control ID, with the evidence bundle attached |
| `failuresOf(run, { owner, now })` | Turn failed results into findings with owner, severity, deadline and fix |
| `syncTickets(tickets, run, options)` | Open one ticket per failure, deduplicated by control and app, and close the ones now fixed |
| `pushRelease(vulns, release, options)` | Send the CycloneDX SBOM and the VEX for a release |
| `importFindings(vulns, release, options)` | Read findings back with their reachability |
| `processPrivacy(privacy, { steps, sign }, options)` | Run each pending privacy request through every driver, and complete it only when all confirm |
| `testConnectors(connectors, options)` | Ping each connector without writing |

Always:

- Credentials come from `secrets`, and every call writes one audit line with no credential in it.
- A connector is never in the request path.
- A privacy request is complete only after every driver confirms, with the signed erasure record attached.

## Examples

### Tickets that close themselves

```ts
import type { AssureRun, Connector } from "mainsdk/connectors";
import { syncTickets } from "mainsdk/connectors";

export function closeFixed(tickets: Connector, run: AssureRun, failedLastTime: string[]) {
  return syncTickets(tickets, run, { owner: "web-team", previous: failedLastTime });
}
```

Run twice, it opens each issue once. A control that passes now gets a comment and its issue is closed.

### A privacy request across drivers

```ts
import type { Connector, PrivacyStep } from "mainsdk/connectors";
import { processPrivacy } from "mainsdk/connectors";

export function erase(
  privacy: Connector,
  steps: PrivacyStep[], // one per driver: db, email, analytics
  sign: (record: { request: string; type: string; subject: string; drivers: string[] }) => Promise<string>,
) {
  return processPrivacy(privacy, { steps, sign });
}
```

## Errors and checks

| Code | When | Fix |
| --- | --- | --- |
| `MAIN-CONNECTOR-0001` | Settings are not usable: an unknown driver, no https address, or `auth` that is not `secret:NAME` (check and runtime) | Name a listed driver and give its settings |
| `MAIN-CONNECTOR-0002` | A call to the tool failed or was refused with a 4xx or 5xx (502) | Read the audit line for the operation, then fix the settings or retry |

`mainsdk check` has a `connectors` gate: a literal `auth` is `MAIN-SECRET-0002` with its file, line and column, and an unknown driver is `MAIN-CONNECTOR-0001`. Each code's cause and fix is in [check codes](https://mainsdk.dev/docs/reference/check-codes).

## Swapping drivers

| Kind | Drivers |
| --- | --- |
| `grc` | `servicenow-irm`, `archer`, `onetrust-grc` |
| `privacy` | `transcend`, `onetrust-privacy` |
| `vulns` | `dependency-track`, `snyk`, `wiz`, `defectdojo` |
| `tickets` | `github`, `jira` |

Change `driver` and its address; the calls and the audit line stay the same. A new driver must pass the `connector@1` conformance suite before it is listed.

## Testing

`@mainsdk/testing` has `runConnectorConformance`, nine cases every driver must pass: it declares `connector@1` and a kind, pushes evidence and findings (repeatably), receives well-formed requests, syncs status with nothing to send, writes nothing on ping, logs every call, takes its credential from secrets, and fails a refused call with a `MainError` that names the call. Point a connector at a mock behind the HTTP port to test your own flow. See [Test your app](https://mainsdk.dev/docs/guides/testing).

## Security and privacy

- A literal token in `main.yaml` fails `mainsdk check` with the line it is on.
- The audit line holds the operation, method, path and status, never the credential or the body.
- Running connectors from a developer's machine or CI is free; scheduled pushes from MainSDK Cloud follow the plan rules.

## Next

- [Connect your tools](https://mainsdk.dev/docs/guides/use-connectors): the steps, end to end.
- [Secrets](https://mainsdk.dev/docs/interfaces/secrets)
- [Privacy](https://mainsdk.dev/docs/interfaces/privacy)
