Skip to main content
Early accessElevarq Analyzer is not yet generally available — this manual documents the current build.Request an evaluation →
Elevarq Analyzer docs

How-to guide

Declarative ticket integrations

Configure your GitHub, Jira, GitLab, and Linear ticket targets from a version-controlled manifest instead of the UI — with credentials supplied by reference (never in the file) from your platform's secret store. Built for GitOps, immutable deployments, and managed secret rotation.

Ticket targets — the GitHub, Jira, GitLab, and Linear destinations Workbench opens tickets in — can be configured two ways. The interactive path is the Integrations settings page: an operator fills in a form and pastes a credential. The declarative path is a version-controlled manifest applied at deployment time, where the credential is a reference to a mounted secret, never a token pasted into a file.

This page covers the declarative path: the manifest schema, how a credential is supplied by reference (secret_ref) from a platform-managed secret store, and how the two paths coexist.

The declarative path exists for the same reasons GitOps does: repeatable configuration, immutable deployments, secret rotation owned by the platform, and a clean separation of duties — the person who writes the manifest never handles the token.

When to use declarative config

Interactive (UI)Declarative (manifest)
Configured byan operator, in the browsera manifest applied at deploy time
Credentialpasted token, encrypted at rest by Workbencha secret_ref to a secret the platform mounts
Best fora quick first target, ad-hoc changesGitOps, immutable/reproducible deployments, managed rotation
Rotationoperator re-enters the tokenthe platform rotates the mounted secret

The two are not exclusive — a deployment can have UI-created targets and manifest-managed targets side by side. Manifest-managed targets are marked as externally managed in the UI (see Coexisting with the UI).

The manifest

A manifest is a YAML (or JSON — YAML is a superset) document listing the targets to configure. It declares a schema version and an integrations array:

version: 1
integrations:
  - platform: github                 # github | jira | gitlab | linear
    name: Production findings
    base_url: https://api.github.com # GitHub Enterprise: https://ghe.example.com/api/v3
    target_identity: acme/findings   # owner/repo (GitHub), project key (Jira), project path/id (GitLab), team key (Linear)
    credential_mode: pat             # see the per-platform setup docs
    secret_ref: file:///run/secrets/github-token
    platform_options: {}             # per-platform non-secret options
    auto_dispatch_min_severity: off  # off | low | medium | high | critical

Fields

FieldRequiredNotes
platformyesgithub, jira, gitlab, or linear.
nameyesDisplay name for the target.
base_urlyesAPI base URL. Public host, or your self-managed / Enterprise host. Linear is fixed to https://api.linear.app/graphql.
target_identityyesowner/repo (GitHub), project key (Jira), numeric id or namespace path (GitLab), team key (Linear — uppercase, e.g. ELE).
credential_modeyesThe authentication mode — the value the platform's setup doc lists (e.g. pat, app_installation, api_token, project_token; Linear: personal_api_key or oauth_bearer).
secret_refyesA reference to the mounted credential. The only place a credential may come from in a manifest.
platform_optionsnoNon-secret per-platform options (e.g. the Jira account email, GitHub App app_id / installation_id, Linear project_id / state_id / assignee_id / cycle_id / extra_label_ids).
auto_dispatch_min_severitynoThreshold that arms automatic dispatch; defaults to off.

⚠️ A manifest never contains a secret. Inline credential fields — credential, token, password, private_key, api_token — are a hard validation error. The credential is always a secret_ref. This keeps tokens out of version control and out of the manifest's blast radius entirely.

External secret references

A secret_ref points at a credential the platform mounts as a file. The launch scheme is file:// with an absolute path:

secret_ref: file:///run/secrets/github-token

Workbench reads the referenced file lazily, at the moment it needs the credential — never at apply time, never into the database, never into a log, an error message, or an audit event. The stored target carries only the reference (a non-secret string), so the secret's lifecycle is entirely the platform's to own.

The contract is deliberately provider-neutral: Workbench consumes a mounted file and nothing else. It embeds no cloud SDKs and holds no cloud IAM credentials. Any secret store that can present a secret as a file works — which, via the Kubernetes Secrets Store CSI driver, covers AWS Secrets Manager, Azure Key Vault, and Google Secret Manager, plus Docker secrets and systemd credentials outside Kubernetes.

Mounting the secret

The examples below all end the same way: the secret lands at a path inside the container, and the manifest's secret_ref is file://<that path>.

AWS Secrets Manager (Kubernetes Secrets Store CSI)

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: workbench-github-token
spec:
  provider: aws
  parameters:
    objects: |
      - objectName: "prod/workbench/github-token"
        objectType: "secretsmanager"

Mount it into the Workbench pod and reference the mount path:

volumes:
  - name: tokens
    csi:
      driver: secrets-store.csi.k8s.io
      readOnly: true
      volumeAttributes:
        secretProviderClass: workbench-github-token
volumeMounts:
  - name: tokens
    mountPath: /mnt/secrets-store
    readOnly: true
# secret_ref: file:///mnt/secrets-store/github-token

The CSI driver authenticates to AWS with the pod's IAM role (IRSA) — the cloud credential never enters Workbench.

Azure Key Vault (Kubernetes Secrets Store CSI)

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: workbench-github-token
spec:
  provider: azure
  parameters:
    keyvaultName: "workbench-kv"
    tenantId: "00000000-0000-0000-0000-000000000000"
    objects: |
      array:
        - |
          objectName: github-token
          objectType: secret
# mount at /mnt/secrets-store; secret_ref: file:///mnt/secrets-store/github-token

Workload identity on the pod authenticates to Key Vault.

Google Secret Manager (Kubernetes Secrets Store CSI)

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: workbench-github-token
spec:
  provider: gcp
  parameters:
    secrets: |
      - resourceName: "projects/my-project/secrets/workbench-github-token/versions/latest"
        path: "github-token"
# mount at /mnt/secrets-store; secret_ref: file:///mnt/secrets-store/github-token

Workload Identity on the pod authenticates to Secret Manager.

Docker secrets

printf '%s' "$GITHUB_TOKEN" | docker secret create github-token -
services:
  workbench:
    secrets:
      - github-token
secrets:
  github-token:
    external: true
# mounted at /run/secrets/github-token; secret_ref: file:///run/secrets/github-token

systemd credentials

[Service]
LoadCredential=github-token:/etc/workbench/secrets/github-token

systemd exposes the credential at $CREDENTIALS_DIRECTORY/github-token (typically /run/credentials/workbench.service/github-token):

secret_ref: file:///run/credentials/workbench.service/github-token

Applying a manifest

Point Workbench at the manifest with WORKBENCH_INTEGRATIONS_MANIFEST:

WORKBENCH_INTEGRATIONS_MANIFEST=/etc/workbench/integrations.yaml

Workbench reconciles the manifest on start. Apply is idempotent and order-independent: a target is keyed by its organization, platform, normalized base URL, and target identity, so

  • a target in the manifest that does not exist yet is created;
  • a target that already exists is updated in place to match;
  • re-applying an unchanged manifest is a no-op.

Running the same manifest against ten identical deployments produces ten identical configurations. A malformed manifest (bad schema version, an inline secret, an invalid secret_ref) fails closed with a specific reason and changes nothing — Workbench does not partially apply.

Reconciliation is additive and updating; it does not delete. A target you remove from the manifest is left in place, not torn down — deleting a target is a deliberate action through the UI or API, so a manifest edit can never silently drop a live integration. Prune intentionally.

Rotation

Because the credential is a mounted file, rotation is the platform's job, not Workbench's. The secret store (or your rotation job) writes the new value to the same mount; Workbench reads the current contents on the next dispatch, so a rotated token is picked up without reconfiguring anything in Workbench. No restart is required for a value change at an existing path.

Changing the reference itself (a new path, a new target) is a manifest change — edit the manifest and re-apply.

Coexisting with the UI and declarative config

A manifest-managed target is visible on the Integrations page like any other, but is clearly marked as externally managed and shows its non-secret source (the secret_ref). There is no secret to display — there is none stored.

Because the manifest is the source of truth for the targets it lists, a UI edit to a manifest-managed target is overwritten on the next reconcile. The UI surfaces this so the change is deliberate: manage those targets through the manifest, and use the UI for interactively-created targets. Deletion is always explicit (UI/API); reconciliation never deletes.

Plan availability

Ticket integrations — interactive and declarative alike — are a Professional, Business, and Enterprise capability, subject to the signed entitlement's allowed platforms and target cap. On Starter they are not included: a manifest applied on a Starter deployment configures nothing, exactly as the Integrations page is unavailable there. Declarative configuration is another way to reach the same capability, never a way around the entitlement.

Security model

The external-secret path is built to keep a mounted credential inside its mount and out of every output surface:

  • Path traversal — a secret_ref must be an absolute file:// path. Relative paths and .. segments are rejected before any filesystem access.
  • Symlink escape — the final path component is opened without following a symlink, so a mount cannot be swapped for a link pointing outside it.
  • TOCTOU — the path is resolved and opened in one step and the opened descriptor is re-checked to be a regular file, bounding the window between check and use.
  • Redaction — the secret bytes are never persisted, logged, returned in a response, or written to an audit event. Error messages carry the non-secret reference path only.
  • Provider unavailable — an absent or unreadable mount is a clean, retryable failure that leaves the stored configuration intact; the target's connection test reports it so an operator can act on it.

Mount permissions are the platform's responsibility. A world- or group-readable mount is a warning, not a hard failure — tighten it at the mount, where it belongs.

Next

  • Security posture — Workbench's overall security posture and the master key that protects UI-stored credentials.
  • Database registration — the other half of onboarding: the databases whose findings become tickets.

Run Workbench

docker pull ghcr.io/elevarq/workbench:v0.1.0

Pin a digest in production — verify the image.