Custom Connector — Integration Setup
What this integration is for: every other integration in this catalog names a vendor CMDash has written an adapter for. This one does not. You describe an HTTP/REST source yourself — its URL, how to authenticate, and which fields to read — and CMDash syncs it on a schedule exactly like a built-in connector. No CMDash release, and no waiting for your vendor to be added.
When to use this, and when not to
Use a custom connector when the system you need evidence from has a JSON HTTP API and is not in the catalog: an internal service, a regional vendor, a niche tool, or something your team built.
Do not use it to replace a built-in connector. A built-in adapter normalizes a vendor's data into CMDash's own shapes — vulnerabilities, endpoints, identity coverage — and feeds posture scoring and continuous compliance from them. A custom connector produces ATTESTED-tier maturity observations, which is a deliberately weaker claim (see Evidence tier).
Prerequisites
- CMDash Command tier or above
- The
custom-connectorsfeature enabled for your workspace - OWNER or ADMIN role
- A source that returns JSON over HTTPS, reachable from the public internet
- A credential for that source, if it needs one
Reachability. CMDash's sync workers call your endpoint from the public internet. A source on a private network, behind a VPN, or bound to localhost cannot be reached, and CMDash will refuse to save a URL that resolves to a private or link-local address — this is an anti-SSRF control and is not configurable.
Step 1: Describe the request
- Go to Integrations → Custom Connectors → New connector.
- Give it a name unique within your workspace, and an optional description.
- Enter the endpoint URL. It must be
http://orhttps://and must resolve to a public address. - Choose the method (
GETby default). If you pickPOST, supply a request body template. - Add any static headers your source needs.
Step 2: Choose an auth style
| Auth type | What you supply | How CMDash sends it |
|---|---|---|
NONE | nothing | no credential |
API_KEY_HEADER | the key, and header name | <Your-Header>: <key> (defaults to X-API-Key) |
API_KEY_QUERY | the key, and param name | ?api_key=<key> (name configurable) |
BEARER_TOKEN | the token | Authorization: Bearer <token> |
BASIC_AUTH | username and password | Authorization: Basic <base64> |
OAUTH2_CLIENT_CREDENTIALS | client id, secret, token URL | CMDash mints a token, then sends it as a bearer |
Credentials are encrypted at rest through the same key-managed path every other connector's credentials use, and are never returned by any API — once saved, a credential can be replaced but not read back.
Authorization-code OAuth2 is not supported. That flow requires you to register an OAuth application and consent through a browser redirect, which a scheduled server-side sync cannot do. If your source only offers three-legged OAuth, it needs a real adapter — tell CMDash support which source it is.
Step 3: Map the fields
A mapping has three parts: a JSONPath into the response, a target field name, and where the value lands.
CMDash supports a deliberately narrow JSONPath subset:
| Syntax | Meaning |
|---|---|
$ | the whole response |
.name | an object member |
['name'] | an object member whose name needs quoting |
[0] | an array element by index |
[*] | every element of an array or object |
Filter expressions ([?(...)]), recursive descent (..), slices ([1:3]) and unions ([0,1]) are not supported and are rejected when you save. This is a security decision, not an omission: those are the constructs that make a JSONPath engine evaluate code, and a mapping you author runs on CMDash's servers. A path using one of them fails immediately with an explanation, rather than quietly matching nothing at 3am.
Examples against {"data":{"items":[{"id":"a","score":1}],"total":1}}:
$.data.total -> 1
$.data.items[0].id -> "a"
$.data.items[*].score -> [1]
Where a mapped value lands
Each mapping lands as a maturity observation:
- Observation — the value becomes a
MaturityObservationunder a source class you pick from a fixed list. Observations flow into maturity scoring and continuous compliance. The list is fixed on purpose: a source class is part of CMDash's scoring vocabulary, so a tenant selects an existing one rather than inventing a label that nothing downstream understands. - There is no Evidence destination. An earlier version of the builder offered one, but it never stored anything: a compliance evidence record has to name the framework and control it supports, and a mapped value has none. The builder now refuses it, and a connector saved with one still syncs but reports each such mapping as ignored (the sync shows as partial). If no source class describes your field, attach the supporting file to the control as evidence yourself.
Step 4: Dry-run before you save
Dry run issues the real request against your real endpoint and shows you the raw response alongside what each mapping extracted — but writes nothing. Use it until every mapping resolves. A mapping that matches nothing is reported as a mapping error, not silently skipped.
Step 5: Schedule
Pick a sync interval. Custom connectors get their own rate-limit budget and their own circuit breaker per connector, so a flaky endpoint slows or trips only itself and never the rest of your integrations.
Evidence tier
Every observation a custom connector produces is recorded at the ATTESTED tier — the same tier as something a person typed in — never OBSERVED or VALIDATED.
This is deliberate and is not a limitation to work around. CMDash cannot corroborate a source it knows nothing about; a connector can be pointed at any URL that returns agreeable JSON. Treating self-configured data as independently observed would let a workspace raise its own credibility score by pointing a connector at a file it wrote. Auditors ask where a number came from, and "a source the customer configured themselves" is the honest answer.
Limits
- Response bodies are capped at 2 MiB.
- A single
[*]mapping yields at most 1,000 values. - Redirects are never followed automatically.
- Each sync makes ONE request to your endpoint (no paging): if your API paginates, the mapped values come from the first page only. Point the connector at an endpoint that returns the whole summary you need.
- Inbound webhooks are not supported — CMDash polls your source; your source cannot push to CMDash. (For push-based ingest, see the signed evidence-push API.)
- Continuous-compliance rules cannot yet target a specific custom connector when a workspace has more than one. This is known and tracked.
Troubleshooting
| Symptom | Cause |
|---|---|
| "URL host resolves to a private address" | The endpoint is not publicly reachable. This is an anti-SSRF control. |
| "target did not return valid JSON" | The source returned HTML or plain text — often an auth page. |
| "matched nothing in the response" | The JSONPath is right-looking but wrong. Re-run a dry run and compare. |
| "circuit breaker open" | This connector failed repeatedly and is cooling off. Only it is affected. |
| Invalid JSONPath on save | You used a filter, slice, union or ... Rewrite it as an explicit path. |