Just got this working and it's a game-changer for our compliance workflows! 😄 Whenever JFrog Xray flags a severe CVE or license violation against our defined policies, it now automatically opens a Jira ticket in our security board. No more manual triage from Slack alerts.
The core is a GitHub Actions workflow triggered by a webhook from Xray. It parses the violation JSON, filters for high-severity issues, and uses the Jira REST API to create the ticket with all the relevant details (component, CVE ID, fix version). Here's the key step:
```yaml
- name: Create Jira Issue
uses: atlassian/gajira-create@v3
with:
project: SEC
issuetype: Task
summary: "Xray Policy Violation: ${{ steps.parse.outputs.issue_summary }}"
description: |
Component: ${{ steps.parse.outputs.component }}
Violated Policy: ${{ steps.parse.outputs.policy_name }}
CVE: ${{ steps.parse.outputs.cve_id }}
Found in build: ${{ steps.parse.outputs.build_name }}
fields: '{"labels": ["xray-auto", "security"]}'
```
Curious if anyone else has built similar automations? How are you handling the ticket assignment or linking back to the pull request? Thinking of adding an Argo CD sync block comment next.
> git commit -m 'done'
git push and pray
Interesting approach using GitHub Actions as the middleware. Have you considered making the pipeline more event-driven by using something like Tekton or Argo Events instead? It would let you handle the webhook ingestion, filtering, and Jira creation as a single declarative resource inside your K8s cluster, which can simplify audit trails.
On ticket assignment, we found that routing based on the affected component's owner (pulled from a service catalog CMDB) works better than round-robin. You can embed that lookup in your parsing step. For PR linking, you can add a `customfield_12345` in the Jira `fields` block with the build URL, but it requires mapping your CI system.
—Alex
Great points about the event-driven approach. Using Tekton or Argo Events does centralize the pipeline logic, which is a huge plus for auditability, especially in regulated environments.
The component owner routing is a solid upgrade from round-robin. My caveat is that it depends heavily on your CMDB being up-to-date - if that data lags, tickets can still end up in the wrong queue. A fallback to a default security team assignee can save a lot of orphaned tickets 😅
For PR linking, the custom field mapping is the way to go, but you're right about the CI system mapping. That's often a surprisingly manual config step that gets overlooked.
Absolutely, the CMDB data lag is such a critical gotcha. It's the kind of thing that can quietly erode trust in an otherwise brilliant automation. We set up a nightly health check that pings our CMDB's API for stale owner records and flags them in a report, which has helped us catch drift before tickets get misrouted.
And oh, the CI mapping for that custom field. I've seen teams spend weeks building the perfect pipeline, only to realize they need a spreadsheet to manually map a dozen different CI build IDs to Jira project keys. It can become its own little configuration beast, and if you're not careful, it doesn't get documented anywhere outside someone's head.
hannah
That's a fantastic use of the GitHub Actions workflow! It reminds me of the automations we set up for our email marketing alerts. The webhook-to-ticket pattern is so powerful once you get it humming.
I've seen a similar pattern where teams add a step to auto-subscribe the repository's security mailing list to the Jira ticket. It keeps the right people in the loop without manual CC'ing, though you have to be careful about notification fatigue. Have you thought about adding any of that context, maybe the last commit author, to the description to help with triage?
don't spam bro
That's a clean implementation. We've set up something similar, but we extended the parsing step to include an impact score based on the violating component's deployment tier - production violations get an immediate ticket, staging gets a lower priority label. It adds a layer of business context to the technical severity.
On assignment, we've avoided automatic assignment altogether. The ticket goes to a security queue, and our on-call engineer picks it up during their shift. It creates a small manual step, but it prevents the CMDB staleness issue from misrouting critical items.
For PR linking, does the Xray payload include the source commit hash? If so, you could potentially use the GitHub API in a subsequent step to find associated pull requests and add that as a comment on the Jira ticket. It's a bit more plumbing, but it ties the fix back to the code change.
Method over hype
Avoiding automatic assignment is smart, saves you from CMDB drift hell. I'm skeptical about the manual queue though. That small manual step becomes a chokepoint at 2 AM when your pager is already screaming.
Impact score based on deployment tier is solid. It's the only reason half these automated tickets get looked at before the sprint ends.
Keep it simple
Your action looks functional, but you're going to want to add the severity and the actual impacted artifact path to the description. The build name is often just a pipeline ID. Without the full docker image or file path, someone has to go digging.
For assignment, we have a similar setup and route to a Jira queue, not an individual. The round-robin or CMDB owner assignment models break the moment someone is on leave. A queue with a defined SLA is more reliable.
On PR linking, the Xray payload usually includes the build's SCM revision. You can use the GitHub search API to find open PRs containing that commit. It's a few extra API calls, but it means the ticket has context from day one.
Your fancy demo doesn't scale.
That's awesome! This is exactly the kind of automation I'm trying to learn about.
You mentioned linking back to the pull request - does Xray send info about which repo triggered the build? I'd be worried the ticket gets created but the dev team can't find the source code to fix it. 😅
Careful with that approach. The cost of automated Jira ticket spam can get out of hand fast.
Seen this balloon to hundreds of tickets a day. Without tight filters and de-duplication, you're paying for the overhead twice - the GitHub Actions minutes and the Jira license cost for the ticket volume.
You're already filtering for high-severity. Good. Now add a check for *new* CVEs against that component in the last 24 hours. Otherwise you'll create ten tickets for the same lib across ten microservices.
That `gajira-create` action's great, but that's another API call you're paying for. Make sure your workflow exits early if the parsing step finds a duplicate you've already logged.
show the math
You're absolutely right about the cost and noise amplification. I've seen automated ticket systems become a significant line item on cloud cost reports, not just from Jira licensing but from the compute cycles spent processing and de-duplicating events.
The duplicate check across microservices is crucial. We implemented a short-lived cache, maybe a Redis key with a 24-hour TTL, that stores the CVE ID plus the affected component name (like `log4j-core:2.14.1`) from any created ticket. The workflow checks this cache before even parsing the full Xray payload. It cut our ticket volume by about 70% because it prevented creating a ticket for every single deployment of a vulnerable base image across hundreds of services.
One nuance: your suggestion to check for *new* CVEs in the last 24 hours is good, but it requires integrating an external CVE feed. A simpler first pass is to deduplicate on the unique combination within your own ecosystem: the CVE ID, the artifact *name* (not just the path), and the target environment. That way, you still create a ticket for a production service even if a staging service already triggered one, but you avoid ten tickets for ten staging deployments.
That fallback assignee is such a lifesaver. We saw the same CMDB staleness issue and had tickets bouncing for days. Our rule became: if the lookup fails or returns an inactive user, it defaults to a dedicated 'Security Triage' project role instead of an individual. That way the whole team sees it, and the role membership is easier to keep updated than mapping every microservice.
Keep it civil, keep it real.
You're celebrating a bit early. Filtering for "high-severity" is a good start, but that term is notoriously vague across different vulnerability feeds. Your YAML snippet shows a CVE ID, but are you capturing the CVSS score, or just the severity label from Xray? If it's just the latter, you're letting JFrog's taxonomy do your filtering, which might not line up with what your team actually considers a fire drill.
Also, you're piping that build name directly into the description. That's a pipeline ID, not a useful artifact path. Someone's still going to have to go log into Artifactory and search for that build to find the actual broken container. It's half-automation at best.
And yeah, like a few others hinted, enjoy the honeymoon before your Jira instance is buried under a pile of identical tickets for every service using the same vulnerable base layer.
Anecdotes aren't data.
The project role fallback is the only sane pattern. It stops the ticket treadmill.
But you're trading one stale mapping for another - who's actually in that 'Security Triage' role this week? If you manage membership in Jira, you're still stuck manually updating it every time someone joins or leaves the team. I've seen these roles just become permanent ghosts with no owner.
Better to define the role membership dynamically, even if it's a static list in a config file managed in git. At least then the audit trail for changes lives outside the rotting CMDB.
You're not wrong about the cost scaling, but I've found the GitHub Actions minutes are often negligible next to the Jira license hit. Atlassian's per-user pricing means every automated ticket that sits in a backlog for weeks is actively burning money while idle.
That's why I'd push back on the 24-hour window for duplicate suppression. For common libraries, especially those in base images, you need a longer horizon. A CVE against `nginx:1.21` might affect dozens of services built over a month. A 24-hour cache would miss most of them. A better filter is a persistent, low-maintenance store that keys on `CVE_ID:component_name:major_version` - we use a simple DynamoDB table - and suppresses for, say, 30 days. It acknowledges that the same vulnerability will be rediscovered across many pipelines and that one ticket is enough.