Connectors to GRC, privacy, vulnerability and ticket tools
connectorsPush 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.
connectorsFree in your app, Apache-2.0Status 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.
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 }import { connectorsFrom, pushAssure } from "mainsdk/connectors";
const connectors = connectorsFrom(config.connectors, { http, secrets, log });
await pushAssure(connectors.grc, run); // one control result per failure, evidence attachedCalls
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)
Drivers
12 drivers. Select one to see its line in main.yaml.
connectors:
driver: servicenow-irmChange this one line to swap providers; your calls stay the same.ServiceNow IRM integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-CONNECTOR-0001 | Checkmainsdk check, before deploy | 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 | RuntimeError responses and logs | See the spec |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| connector@1 contract and conformance suite | Phase 3 | 1.2 | connectors#connector-contract |
| GRC connectors | Phase 3 | 1.2 | connectors#grc-connectors |
| Privacy-request connectors | Phase 3 | 1.2 | connectors#privacy-connectors |
| Vulnerability-tool connectors | Phase 3 | 1.2 | connectors#vuln-connectors |
| Ticket connectors | Phase 3 | 1.2 | connectors#ticket-connectors |
| Secrets and audit for every call | Phase 3 | 1.2 | connectors#connector-auth |
Quick start
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 }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
}mainsdk connectors test # dry run against each configured connectorConfiguration
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
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
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.
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.
Security and privacy
- A literal token in
main.yamlfailsmainsdk checkwith 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: the steps, end to end.
- Secrets
- Privacy
Was this page helpful?