MainSDK
Search⌘K
Interfaces

Security and privacy

Connectors to GRC, privacy, vulnerability and ticket tools

connectors

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.

Building for 1.2ships as a beta APIPhase 3 · Mobile and regulatedSpec connectorsFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
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.

At a glance

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 }

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)

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

12 drivers. Select one to see its line in main.yaml.

main.yamlServiceNow IRM
connectors:
  driver: servicenow-irm

Change this one line to swap providers; your calls stay the same.ServiceNow IRM integration

Codes

CodeWhere it appearsMeaning
MAIN-CONNECTOR-0001Checkmainsdk check, before deployA 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-0002RuntimeError responses and logsSee the spec

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
connector@1 contract and conformance suitePhase 31.2connectors#connector-contract
GRC connectorsPhase 31.2connectors#grc-connectors
Privacy-request connectorsPhase 31.2connectors#privacy-connectors
Vulnerability-tool connectorsPhase 31.2connectors#vuln-connectors
Ticket connectorsPhase 31.2connectors#ticket-connectors
Secrets and audit for every callPhase 31.2connectors#connector-auth

Quick start

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 }
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.

KeyMeaning
driverOne of the drivers below
instance or urlThe tool's https address, not needed for github
authsecret:NAME; a literal token fails the check
repo, project, orgThe repository (github), project (jira) or organisation (snyk)

What you get

CallWhat 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

CodeWhenFix
MAIN-CONNECTOR-0001Settings 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-0002A 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.

Swapping drivers

KindDrivers
grcservicenow-irm, archer, onetrust-grc
privacytranscend, onetrust-privacy
vulnsdependency-track, snyk, wiz, defectdojo
ticketsgithub, 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.

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

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet