Skip to main content
Cynative’s GitHub and GitLab connectors are built for read-oriented source-control and repository security research. Both use an in-process per-request classifier to resolve each request to its API category and required access level, then allow it only within the configured ceiling — read-only by default, with certain sensitive categories blocked.

GitHub connector

Connector id: github
Fixed host: api.github.com (GitHub Enterprise Server is not supported)

Quick start

Authenticate the GitHub CLI, then run Cynative from the same environment:
No YAML configuration is required for the default read-only mode.

Credential discovery

Cynative asks the GitHub CLI auth library (go-gh) for a token for github.com. Cynative does not read these variables itself; go-gh does. Because Cynative always queries the literal github.com host, go-gh’s enterprise / GH_HOST branches never apply.
  • GH_TOKEN — highest-precedence GitHub token; read by go-gh for github.com.
  • GITHUB_TOKEN — second-precedence token (also the GitHub Actions built-in token).
  • GH_PATH — path to the gh executable go-gh shells out to for the keyring-fallback token lookup.
  • GH_CONFIG_DIR — directory go-gh reads the gh auth login token (hosts.yml) from.
Registration and validation: If no token is available, the connector is not registered and nothing is printed. When a token is present, Cynative eagerly validates it at startup with a hardened GET /user request (yielding the @login identity shown in the startup inventory). When /user is inconclusive — a GitHub App installation token, an Actions GITHUB_TOKEN, or a rate-limit condition — Cynative falls back to GET /rate_limit, a universally-accessible, rate-limit-exempt endpoint. References:

Target selection

The GitHub connector always targets the public GitHub REST API at api.github.com — the host is hardcoded. It additionally authorizes the GitHub-owned download hosts codeload.github.com, release-assets.githubusercontent.com, and objects.githubusercontent.com (GET/HEAD only) so release-asset and tarball redirects can be followed. Change the identity by changing the token: run gh auth login, or export GH_TOKEN (highest precedence) or GITHUB_TOKEN. The host is not configurable.

Request usage

Use auth_provider: "github". No connector-specific auth object is required.

Hardening

Host pinning

Host pinning allows only api.github.com and the three GitHub-owned download hosts. The download hosts are GET/HEAD-only, regardless of the configured permissions. Redirects are never followed — a 3xx is returned to the model, which must request the Location URL explicitly (re-entering host pinning and action authorization on each hop).

Category + access-level classification

The in-process classifier resolves each REST request to its GitHub category/subcategory and the required access level (read or write), then allows it only when the configured connectors.github.permissions ceiling permits that level for that category.
  • The check runs before the token is attached, so a disallowed request is rejected without the credential ever leaving the process.
  • The classifier is backed by GitHub’s first-party OpenAPI description, distilled into a category routing table, fetched anonymously and cached from raw.githubusercontent.com.
  • A REST request that cannot be classified fails closed. The secret-scanning category is denied by the secure baseline even for reads, because its read endpoints can leak secret material.
  • The GraphQL API (/graphql) is not supported and is always denied.

Secret-scanning protection

Secret-scanning protection is layered: the table admission guard rejects any fetched spec that maps a secret-scanning template to another category; the secret-scanning: none baseline in the default exposure blocks all such requests at the ceiling; and unknown routes fail closed.

Cynative configuration

connectors.github.permissions is a category[/subcategory] → read|write|none ceiling. Omit it entirely for the secure default (read everything except secret-scanning). The most-specific match wins (category/subcategory → category → default). write implies read; none denies even reads.
Set from the environment as a compact comma-separated list:
A non-empty environment value replaces the config-file map wholesale. A blank value is treated as unset. Duplicate keys are rejected at load time. When the ceiling is widened (default=write, an opened secret-scanning, or any category:write override), the startup inventory line is loud (⚠) and names exactly what was widened.

Defense at a glance (GitHub)

GitHub limitations

  • The connector supports api.github.com and the listed download hosts. GitHub Enterprise Server hosts are not supported.
  • Actions-artifact downloads are not supported: GitHub redirects them to per-account Azure blob endpoints, which are outside the GitHub-owned host invariant.
  • A configured permissions ceiling never expands the GitHub token’s own scopes — the effective capability is the intersection of the two. Setting issues: write does nothing if the gh token cannot write issues.
  • There is no client-side credential downscoping. A PAT, gh, or OAuth token has its scopes fixed at creation.
  • The category routing table requires egress to raw.githubusercontent.com. With no network and no local cache, the connector fails closed — every api.github.com request is denied with a github_hardening: category table not ready error.

GitLab connector

Connector id: gitlab
Default host: gitlab.com (configurable for self-managed instances)

Quick start

Authenticate with the GitLab CLI or export a Personal Access Token:
Or with an explicit token:
No YAML is required for the default read-only mode against gitlab.com.

Credential discovery

Cynative resolves a token for the configured host (default gitlab.com) in this order:
  1. Token environment variable — GITLAB_TOKEN, then GITLAB_ACCESS_TOKEN, then OAUTH_TOKEN (glab’s precedence order). The first non-empty value is used, exec-free, and takes precedence over any glab credential.
  2. glab (glab auth credential-helper) — when no token env var is set, Cynative delegates to the pinned glab binary (requires glab v1.85.2+), which reads config.yml or the OS keyring, refreshes an expired OAuth token, and prints the credential as JSON.
Cynative reads these variables to discover the token and locate glab’s config:
  • GITLAB_TOKEN — first-precedence token.
  • GITLAB_ACCESS_TOKEN — second-precedence token.
  • OAUTH_TOKEN — third-precedence token.
  • GLAB_CONFIG_DIR — exclusive override of glab’s config directory.
  • XDG_CONFIG_HOME — adds $XDG_CONFIG_HOME/glab-cli/config.yml as a config location.
If no token env var is set and glab has no usable credential, the connector is a quiet ambient skip. If a glab config exists but the glab binary is missing or too old, the connector is skipped loudly with a steer to install/upgrade glab or set GITLAB_TOKEN. Registration and validation: When a token is discovered, Cynative validates it live at startup with a dial-guarded GET /api/v4/user before registering. An invalid, expired, or unreachable token shows the connector as unavailable with a reason rather than registering and failing every request. GitLab 18.9 or later is required for self-managed instances (when using api_reference; the connector itself still registers but api_reference is unavailable for older instances). References:

Target selection

The GitLab connector targets GitLab SaaS — https://gitlab.com, REST API /api/v4 — by default. The instance is chosen by Cynative config (connectors.gitlab.host, default gitlab.com) and host-pinned. The target is not selected per request; the URL must match the served host. To point at a self-managed instance: set connectors.gitlab.host: gitlab.example.com (or CYNATIVE_CONNECTORS_GITLAB_HOST). Optionally set connectors.gitlab.api_host when API calls go to a different host than the login host, append :port for a non-443 instance, set allow_private_network: true for a private network, and ca_cert for a private CA.

Request usage

Use auth_provider: "gitlab". No connector-specific auth object is required.
For self-managed instances, replace gitlab.com with your configured host (or api_host).

Hardening

Host and port pinning

Host pinning allows only the configured host (default gitlab.com), or api_host when set. The TLS port is pinned too — the request URL’s port must match the configured port (or 443 when none is configured). Redirects are never followed. The connector serves the /api/v4 REST API only. A request whose path is not under /api/v4 is not category-classifiable and fails closed (ErrUnclassifiable).

Per-category exposure classification

The in-process classifier resolves each REST request to its GitLab category and required access level (read or write), then allows it only when the configured connectors.gitlab.permissions ceiling permits that level for that category.
  • The check runs before the token is attached.
  • Reads are GET, HEAD, OPTIONS (plus the POST /api/v4/markdown documentation-render endpoint); everything else is a write.
  • The ci-variables category is denied by the secure baseline (even for reads) because its read endpoints return plaintext CI/CD variable values. Any OpenAPI template whose path contains a variables segment is mapped to the ci-variables category at table-distillation time.
  • The GraphQL API (/api/graphql) is not supported and is always denied.
  • A path with a . or .. segment, raw or percent-encoded, is denied.

Model-supplied-credential rejection

The connector is the sole setter of credential material. The following are rejected: Private-Token, Job-Token, Deploy-Token, X-Gitlab-Static-Object-Token, Authorization, Proxy-Authorization, X-Ms-Authorization-Auxiliary, and Cookie headers; URL userinfo; the GitLab request-parameter credentials token, private_token, access_token, job_token (in query string, urlencoded, multipart, or JSON body); the feed_token / rss_token feed credentials; and the sudo impersonation control. After all checks pass, Cynative injects Authorization: Bearer <token>.

Dial-time IP authorization

By default, internal IP ranges (RFC1918, loopback, link-local including cloud metadata, ULA, IPv4-mapped forms) are denied. Set allow_private_network: true for self-managed GitLab instances on private networks. An unconditional floor remains denied even with the opt-in: loopback, link-local, and all private (ULA) IPv6 (fc00::/7) — ULA IPv6 stays denied because every major cloud parks its IPv6 instance-metadata service there.

CA certificate (self-managed with a private CA)

Set connectors.gitlab.ca_cert to the path of a PEM CA certificate file. skip_tls_verify is not supported — TLS validation cannot be bypassed.

Cynative configuration

Target and connection settings

host is the login host (the key glab uses in config.yml). Append :port to either host or api_host for a non-443 instance (e.g. host: gitlab.internal:8443).

Exposure and authorization

connectors.gitlab.permissions is a category → read|write|none ceiling. Omit it entirely for the secure default (read everything except ci-variables). write implies read; none denies even reads.
A non-empty environment value replaces the config-file map wholesale. A blank value is treated as unset. When the ceiling is widened (default=write, an opened ci-variables, or any category:write override), the startup inventory line is loud (⚠).

Defense at a glance (GitLab)

GitLab limitations

  • CI job tokens (endpoint-restricted) are out of scope. Cynative authenticates via Authorization: Bearer, which accepts PATs, project/group access tokens, and OAuth tokens.
  • The category routing table needs egress to gitlab.com. With no network and no local cache the connector fails closed.
  • Container Registry and Pages use separate hosts and authentication paths not covered by this connector.
  • Subpath-mounted installations (e.g. https://example.com/gitlab/) are not supported.
  • A glab credential requires the glab binary (v1.85.2+) on PATH. Set GITLAB_TOKEN for a fully exec-free path.
  • One instance per run. Cynative registers at most one GitLab provider per session.
  • No runtime credential downscoping. The token’s scopes are fixed at issue time. The permissions ceiling narrows what Cynative will attempt but never expands the token’s own scopes.
  • Private (ULA) IPv6 instances are unsupported even with allow_private_network: true. Front a self-managed instance with an RFC1918 IPv4 or global-unicast IPv6 address instead.
  • A literal token field in a request body is rejected. GitLab reads params[:token] (the pipeline-trigger credential) from any body encoding, so any model-supplied token field fails closed. Use the Bearer-authenticated POST /projects/:id/pipeline endpoint instead of the trigger endpoint.
  • skip_tls_verify is not supported. Use ca_cert for self-managed instances with a private CA.

Combined YAML configuration

A full configuration showing both connectors: