Gatetrail

Overview

Gatetrail is offered to businesses only. It has two inputs. The SonarQube™ webhook reports quality gate results. A small relay in your CI pipeline reads open findings from your SonarQube after each scan and pushes them to the app. Both deliveries are signed with secrets you generate on the admin page. Everything else runs inside Atlassian's platform.

Start without touching CI. A Jira administrator and someone who can administer the SonarQube project are enough for the first two steps: bind a SonarQube project to a Jira project, then register the webhook. From the next analysis on, a failed quality gate opens a ticket per project and branch, which is updated while the gate stays red and closes itself when it passes. No change to your pipeline and no SonarQube token in Jira.

Then add the relay. Step 3 is for the team that owns the CI pipeline. The relay adds finding tickets per rule, deadlines, the one-off import, back-sync of risk decisions, and the data for the project page, the dashboard gadget and the portfolio report. The admin page generates the snippets for steps 2 and 3.

Requirements

  • SonarQube™ Community Build (tested with 26.9) or SonarQube™ Cloud (tested October 2026), able to send webhooks to the app's URL over HTTPS.
  • SonarQube Cloud: quality gate tickets, finding tickets, the project overview and back-sync of risk decisions. Branch analysis and branch tickets need a Cloud plan with branch analysis; the Free plan analyses the main branch only. On Free the relay uses a personal user token, since Free has no organization tokens; back-sync needs a token with Administer Issues.
  • SonarQube™ Server is not yet tested.

Weighing the options first? The guide How to turn quality gate results into Jira tickets compares the general approaches.

1. Bind a project

Open Jira settings → Apps → Gatetrail. Under Bindings, choose:

SettingMeaning
SonarQube sourceThe SonarQube server; one default source exists, more can be added (see several servers).
Sonar projectThe SonarQube project key.
Jira project and issue typeWhere tickets are created. Fixed once tickets exist, so they are never split across projects.
LabelsAdded to every ticket.
Auto-closeClose the quality gate ticket when the gate passes again, and finding tickets whose findings are gone.
Minimum severityRelay only. info, low, medium, high or blocker; groups below it get no ticket.
SLARelay only. Days to fix per severity; see deadlines.
Field defaultsValues for other Jira fields on new tickets, such as a component. Fields Gatetrail manages always win.

Under SonarQube sources, the same page shows the trigger URL and generates the webhook secret (needed for step 2) and the relay secret (needed only for step 3). Each secret is shown once when generated; rotate it there at any time. Quality gate results of projects without a binding are ignored.

2. Register the quality gate webhook

In SonarQube, open the project's Project Settings → Webhooks and create a webhook with the trigger URL and the webhook secret from the admin page. The secret is required: deliveries without a valid signature are rejected. Your SonarQube server must be able to reach the trigger URL over HTTPS; it needs no inbound access.

Alternatively, use the command the admin page shows with your trigger URL filled in. It looks like this:

curl -u "$SONAR_TOKEN:" -X POST "$SONAR_URL/api/webhooks/create" \
  -d project=my-project -d name=jira \
  --data-urlencode "url=<trigger URL from the admin page>" \
  -d "secret=$WEBHOOK_SECRET"

On SonarQube™ Cloud the API call also needs -d organization=<your organization key>; once the relay has sent a project overview, the admin page adds it to the command. Creating the webhook under Project Settings → Webhooks needs no organization.

A failed quality gate now opens a ticket per project and branch, with the failed conditions and a link to SonarQube; it is updated while the gate stays red, closed when it turns green and reopened if it fails again. The Sync column of the binding shows when the last result arrived. That is the whole setup for quality gate tickets; step 3 is optional.

3. Add the relay to your CI (optional)

This step is for the team that owns the CI pipeline. It adds finding tickets and everything built on them: deadlines, risk decisions, the one-off import and the project overviews. Run the relay after the SonarQube scan. The scanner must run with sonar.qualitygate.wait=true, so the analysis is complete when the relay reads it. Store SONAR_URL, SONAR_TOKEN, TRIGGER_URL and RELAY_SECRET as masked CI variables. A project analysis token is enough for syncing.

GitLab CI

gatetrail:
  stage: .post
  image: node:22-alpine
  variables:
    SONAR_PROJECT: my-project
  script:
    - npx -y @gatetrail/relay@0.6.5 sync

GitHub Actions

- name: Sync SonarQube findings to Jira
  run: npx -y @gatetrail/relay@0.6.5 sync
  env:
    SONAR_URL: ${{ secrets.SONAR_URL }}
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
    SONAR_PROJECT: my-project
    TRIGGER_URL: ${{ secrets.GATETRAIL_TRIGGER_URL }}
    RELAY_SECRET: ${{ secrets.GATETRAIL_RELAY_SECRET }}

Pin the relay version in CI. Each run sends one ticket update per rule with open findings, then a list of all rules found, so tickets of rules that disappeared can close. The relay only sends outbound; your SonarQube needs no inbound access.

One-off import

To bring existing findings into Jira once, use export. Check first with --dry-run, then send with --yes, at most --max-tickets groups (default 50). Groups that already have a ticket are skipped, so a second run creates nothing new.

npx -y @gatetrail/relay@0.6.5 export --group file --min-severity high --dry-run
npx -y @gatetrail/relay@0.6.5 export --group file --min-severity high --yes --max-tickets 100

Options: --group rule|file|finding, --rule KEY (repeatable), --min-severity LEVEL, --quality MAINTAINABILITY|RELIABILITY|SECURITY (repeatable), --created-after DATE, --project KEY, --branch NAME.

Deadlines and JQL

With an SLA on the binding, every finding ticket gets a due date: days to fix for its severity, counted from the group's oldest open finding. Changing the SLA moves the due dates of open tickets on the next sync; removing it clears them. A daily check labels overdue tickets sla-breached with one comment and removes the label once the deadline is met or gone.

Gatetrail indexes these fields for JQL:

JQL fieldContentExample
sonarProjectSonarQube project keysonarProject = "my-project"
sonarRuleRule key of a finding ticketsonarRule = "typescript:S1523"
qualityGateGate status of a gate ticket: passed or failedqualityGate = "failed"
sonarSlaDueSLA due datesonarSlaDue <= endOfWeek()

Risk decisions

Set an approver group under Risk decisions on the admin page. Its members open Gatetrail decision from a finding ticket's actions menu and either accept the risk or mark a false positive, with a reason of at least 10 characters. Gatetrail then comments on the ticket, closes it as the approver, and logs the decision.

  • Decisions are signed with an Ed25519 key kept in Forge secret storage. The signature covers the ticket and the exact SonarQube project, branch and rule; the relay verifies it and ignores decisions that do not verify.
  • If Jira refuses the comment or the transition, for example for missing permissions, the decision is withdrawn and nothing reaches SonarQube.
  • A decision covers the findings that existed when it was taken. If new findings reopen the ticket, the old decision is removed and a new one is needed.
  • The decision log on the admin page lists the newest first and exports as CSV for audits. Decisions are kept for as long as the app is installed; uninstalling deletes them, see Before you uninstall.

Back-sync of decisions

The relay's backsync command carries decisions to SonarQube: accepted risks become Accepted, false positives False Positive, each with the date, the reason and the Jira ticket as a comment; approvers' names are not written to SonarQube. Tickets closed any other way get one comment on their open SonarQube issues, because SonarQube closes truly fixed issues on its next analysis by itself. Open tickets get their Jira link commented on their SonarQube issues once.

Back-sync needs a SonarQube token with Administer Issues, read access to Jira for an Atlassian service account, and the app's public keys. The relay signs in with the service account's OAuth 2.0 credential (client credentials), never with a person's password or API token, and calls Jira through api.atlassian.com.

Set up the service account once per site; this needs an organization admin and the centralized user management experience. As of October 2026, Atlassian's documentation gives every organization five service accounts at no charge (more with Atlassian Guard Standard or Enterprise), and they do not count towards the Jira user limit; check it for the current terms.

  1. In Atlassian Administration, go to Directory → Service accounts and create one, e.g. gatetrail-backsync, with the User role for Jira.
  2. Give it Browse projects in every Jira project bound to Gatetrail.
  3. Select it, choose Create credentials → OAuth 2.0, and select these read-only scopes: read:issue-details:jira, read:field:jira, read:field.default-value:jira, read:field.option:jira, read:group:jira, read:issue.property:jira, read:issue-meta:jira, read:avatar:jira, read:issue.changelog:jira. Where classic scopes are offered, read:jira-work alone covers the same.
  4. Store client ID and secret as masked CI variables; Atlassian shows them only once. The site's cloud id is on the admin page under Risk decisions.
JIRA_URL=https://your-site.atlassian.net JIRA_CLOUD_ID=<from the admin page> \
JIRA_CLIENT_ID=… JIRA_CLIENT_SECRET=… \
GATETRAIL_DECISION_KEYS=<value from the admin page> \
npx -y @gatetrail/relay@0.6.5 backsync --dry-run

Revoking the credential in Atlassian Administration stops the back-sync at once.

Copy GATETRAIL_DECISION_KEYS from Risk decisions on the admin page. Rotating the signing key adds a new key and keeps older ones listed, so decisions made before still verify; update the variable after rotating.

Several servers

Add a source per SonarQube server on the admin page. Each source has its own trigger URL (with ?source=<id>) and its own secrets, so one server can never write another server's tickets. For back-sync against a source other than the default, set GATETRAIL_SOURCE=<id>. A removed source id is never handed out again.

Views and reports

  • Issue panel on every Gatetrail ticket, with a link to SonarQube: on a quality gate ticket the gate, its status and the analysis time; on a finding ticket rule, severity, count, analysis time, deadline, age and effort.

The following views show data that only the relay delivers; with the webhook alone they list the bound projects without an overview.

  • Project page in each bound Jira project: quality gate, measures and trends per branch, and security findings by OWASP Top 10, CWE Top 25 and ASVS.
  • Dashboard gadget for one project or the whole portfolio.
  • Gatetrail portfolio under Apps: all projects you can browse, printable and as PDF.

Relay reference

VariableNeeded byNotes
SONAR_URL, SONAR_TOKEN, SONAR_PROJECTallAn analysis token is enough for sync and export; backsync needs Administer Issues.
SONAR_BRANCHallOptional; defaults to the main branch.
TRIGGER_URL, RELAY_SECRETsync, exportFrom the admin page.
JIRA_URLbacksyncThe site; used for ticket links.
JIRA_CLOUD_IDbacksyncThe site's cloud id, from the admin page.
JIRA_CLIENT_ID, JIRA_CLIENT_SECRETbacksyncOAuth 2.0 credential of a service account that can browse the bound projects; see Back-sync.
GATETRAIL_DECISION_KEYSbacksyncThe app's public signing keys; without them no decision is applied.
GATETRAIL_SOURCEbacksyncOnly for a source other than the default.

The relay has no runtime dependencies and is open source under the MIT license: npm.

Operations and limits

  • Duplicates: each ticket carries a fingerprint of its project, branch and group, and a delivery always looks for the existing ticket first, also after an interrupted run. One narrow case remains: if the app is interrupted right after creating a ticket and Jira's search index then takes more than two minutes to list it, a retry can open a second ticket. Close the newer one as a duplicate; the next analysis keeps the other up to date.
  • Sync status: the Sync column of each binding shows the last complete sync and the errors per branch. An error stays until a later analysis of the same branch runs without it, so a ticket that failed in one part of an analysis is not hidden by the rest of it succeeding; a quality gate result never clears a relay error or the other way round. Errors Jira will not accept on a retry, such as a required field without a default value or a workflow without a transition to done, are marked needs attention; fix the cause and the next analysis applies again. A ticket Jira refuses to close does not hold up the others; it is tried again with the next analysis.
  • Tickets stopped updating? Use Reset sync on the binding. It forgets which analysis was applied last, for example after a delivery with a wrong clock; tickets and links stay, and existing tickets are updated rather than created again. Deliveries dated more than an hour in the future are rejected in the first place.
  • Retention: branches without activity for 90 days, such as merged pull-request branches, are forgotten, and so is the sync state of a deleted binding; their tickets stay in Jira. A branch SonarQube reports as main is never forgotten.
  • Limits: up to 100 locations are listed per ticket (the count stays exact), and up to 4,000 rules per project and branch per sync. Forge accepts 500 deliveries a minute per Jira site, shared by all pipelines; the relay sends at most 120 a minute (GATETRAIL_MAX_PER_MINUTE), so four pipelines can run at once. Should the site's limit be reached anyway, the app answers 429 and the relay waits, halves its pace for the rest of the run and retries for up to three minutes. Projects with more than 10,000 open issues in one language are read rule by rule from SonarQube's rule catalogue. A rule that still fails is sent again by the next run; its ticket is not closed meanwhile.
  • License: without an active license, the app is read-only: it ignores deliveries, records no decisions and keeps settings as they are. Tickets, reports and the decision log stay available.

Before you uninstall

Uninstalling Gatetrail deletes the data it stores, following Atlassian's data lifecycle for Forge apps: sources and bindings, the webhook and relay secrets, the decision signing keys, sync state, overviews and the decision log. A reinstall starts empty; the earlier data is not restored automatically and may not be restorable at all. Tickets, comments and issue properties are Jira data and stay in your site.

  • Export the decision log as CSV on the admin page and keep it with your audit records.
  • Run the back-sync before you uninstall if decisions are still waiting for it.
  • Keep the decision keys: uninstalling deletes the signing keys, and a reinstall creates a new one. Decisions already on tickets keep their signatures, and the relay verifies them against the public keys in GATETRAIL_DECISION_KEYS, not against the app. Before you uninstall, copy the current value from Risk decisions on the admin page. After a reinstall, append the new value to it, separated by a comma, instead of replacing it; decisions signed with a key that is no longer listed are ignored. Decisions for an additional SonarQube server only apply if you add its source with the same id again.
  • After a reinstall, set up sources and bindings again and copy the new webhook and relay secrets into SonarQube and your CI; the old secrets are rejected.

Atlassian keeps the deleted storage for 28 days. Within 21 days of uninstallation, we can ask Atlassian to relink it to a new installation, only with your consent; Atlassian decides on the request, so do not rely on it.

Questions or problems: support@gatetrail.com. We aim to respond within two business days (Monday to Friday, CET); response times are targets, not a service level agreement. Requests that Atlassian forwards to us as critical are answered within 24 hours.