DevOpsil
JFrog Artifactory
92%
Needs Review

JFrog Xray: Vulnerability Scanning for Your Artifact Pipeline

Amara OkaforAmara Okafor6 min read

JFrog Xray: Vulnerability Scanning for Your Artifact Pipeline

Scanning a container image with a standalone tool like Trivy is a good start. But if your team uses Artifactory as a registry, JFrog Xray takes a different approach: it scans artifacts as they're indexed in Artifactory and blocks promotion or download automatically based on policies you define. No separate scan step in CI, no images that "passed CI" but never got scanned.

This guide sets up Xray scanning, configures policies and watches, and integrates the results into your CI/CD pipeline.

How Xray Works

Xray is a separate service that integrates tightly with Artifactory:

  1. You index a repository in Xray (once)
  2. Every artifact pushed to that repo gets scanned automatically
  3. Xray evaluates the artifact against your security and license policies
  4. If a policy is violated, Xray can block download, fail a build, or just alert

The key difference from standalone scanners: Xray knows the entire component graph. It scans the artifact plus all its transitive dependencies — a Docker image layer might contain a Node.js app that depends on a library with a CVE three levels deep.

Enable Indexing on Your Repositories

ARTIFACTORY_URL="https://yourcompany.jfrog.io"
TOKEN="your-admin-token"

# Enable Xray indexing on docker-dev-local
curl -u "admin:${TOKEN}" \
  -X PUT \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/artifactory/api/repositories/docker-dev-local" \
  -d '{"xrayIndex": true}'

# Enable indexing on docker-prod-local
curl -u "admin:${TOKEN}" \
  -X PUT \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/artifactory/api/repositories/docker-prod-local" \
  -d '{"xrayIndex": true}'

# Trigger a retroactive scan on existing artifacts
curl -u "admin:${TOKEN}" \
  -X POST \
  "${ARTIFACTORY_URL}/xray/api/v1/index" \
  -H "Content-Type: application/json" \
  -d '{
    "repo_paths": ["docker-dev-local/", "docker-prod-local/"]
  }'

Create a Security Policy

Policies define what constitutes a violation. Create a policy that blocks anything with a Critical or High CVE with a CVSS score above 7:

curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v2/policies" \
  -d '{
    "name": "block-critical-cves",
    "description": "Block Critical and High CVEs with CVSS >= 7",
    "type": "security",
    "rules": [
      {
        "name": "critical-cve-rule",
        "priority": 1,
        "criteria": {
          "min_severity": "High",
          "cvss_range": {
            "from": 7.0,
            "to": 10.0
          },
          "fix_version_dependant": false,
          "applicable_cves_only": false
        },
        "actions": {
          "webhooks": [],
          "mails": ["[email protected]"],
          "fail_build": true,
          "block_download": {
            "active": true,
            "unscanned": false
          },
          "block_release_bundle_distribution": true,
          "notify_deployer": false,
          "notify_watch_recipients": true,
          "create_ticket_enabled": false
        }
      }
    ]
  }'

Create a separate license compliance policy:

curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v2/policies" \
  -d '{
    "name": "license-compliance",
    "description": "Block GPL and AGPL licenses in production",
    "type": "license",
    "rules": [
      {
        "name": "no-gpl-in-prod",
        "priority": 1,
        "criteria": {
          "banned_licenses": ["GPL-2.0", "GPL-3.0", "AGPL-3.0"],
          "allow_unknown": false,
          "multi_license_permissive": false
        },
        "actions": {
          "fail_build": false,
          "block_download": {
            "active": false
          },
          "mails": ["[email protected]"],
          "notify_watch_recipients": true
        }
      }
    ]
  }'

Create Watches to Apply Policies

Watches link repositories to policies:

# Watch the prod repo with both policies
curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v2/watches" \
  -d '{
    "general_data": {
      "name": "prod-security-watch",
      "description": "Security and license watch for production images",
      "active": true
    },
    "project_resources": {
      "resources": [
        {
          "type": "repository",
          "name": "docker-prod-local",
          "bin_mgr_id": "default",
          "filters": [
            {
              "type": "package-type",
              "value": "Docker"
            }
          ]
        }
      ]
    },
    "assigned_policies": [
      {
        "name": "block-critical-cves",
        "type": "security"
      },
      {
        "name": "license-compliance",
        "type": "license"
      }
    ],
    "watch_recipients": ["[email protected]"]
  }'

Query Xray from CI/CD

Use the Xray build scan API to fail your CI pipeline when violations are found:

# In your CI pipeline — scan a specific build
# First, publish build info to Artifactory
jfrog rt build-collect-env BUILD_NAME BUILD_NUMBER
jfrog rt build-publish BUILD_NAME BUILD_NUMBER

# Then scan the build with Xray
jfrog bs BUILD_NAME BUILD_NUMBER \
  --fail=true \
  --format=simple-json \
  --server-id=my-artifactory

# Or use the REST API directly
curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v1/scanBuild" \
  -d '{
    "artBuildName": "my-app",
    "artBuildNumber": "42",
    "rescan": false,
    "filters": {
      "min_severity": "High"
    }
  }'

GitHub Actions example:

# .github/workflows/security-scan.yml
name: Build and Security Scan

on:
  push:
    branches: [main]

jobs:
  build-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup JFrog CLI
        uses: jfrog/setup-jfrog-cli@v4
        env:
          JF_URL: ${{ secrets.ARTIFACTORY_URL }}
          JF_ACCESS_TOKEN: ${{ secrets.ARTIFACTORY_TOKEN }}

      - name: Configure build info
        run: |
          jf rt build-collect-env ${{ github.repository }} ${{ github.run_number }}

      - name: Build Docker image
        run: |
          jf docker build \
            --build-name=${{ github.repository }} \
            --build-number=${{ github.run_number }} \
            -t ${{ secrets.ARTIFACTORY_URL }}/docker/myapp:${{ github.sha }} .

      - name: Push to Artifactory
        run: |
          jf docker push \
            --build-name=${{ github.repository }} \
            --build-number=${{ github.run_number }} \
            ${{ secrets.ARTIFACTORY_URL }}/docker/myapp:${{ github.sha }}

      - name: Publish build info
        run: |
          jf rt build-publish ${{ github.repository }} ${{ github.run_number }}

      - name: Xray security scan
        run: |
          jf bs ${{ github.repository }} ${{ github.run_number }} \
            --fail=true \
            --format=table
        # Pipeline fails here if Critical/High CVEs found

View and Triage Violations

# List all violations in a repository
curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v1/violations" \
  -d '{
    "filters": {
      "watch_name": "prod-security-watch",
      "violation_type": "Security",
      "min_severity": "High"
    },
    "pagination": {
      "order_by": "created",
      "direction": "desc",
      "limit": 25,
      "offset": 0
    }
  }'

# Get scan summary for a specific artifact
curl -u "admin:${TOKEN}" \
  "${ARTIFACTORY_URL}/xray/api/v1/summary/artifact" \
  -d '{
    "paths": ["default/docker-prod-local/myapp/1.0.0/manifest.json"]
  }'

CVE Exception Workflow

Not every CVE is exploitable in your context. Xray lets you create exceptions (called "Ignore Rules"):

curl -u "admin:${TOKEN}" \
  -X POST \
  -H "Content-Type: application/json" \
  "${ARTIFACTORY_URL}/xray/api/v1/ignore_rules" \
  -d '{
    "notes": "CVE-2023-44487 (HTTP/2 rapid reset) — mitigated at load balancer level",
    "expiration_date": "2026-06-01",
    "ignore_filters": {
      "cves": ["CVE-2023-44487"],
      "watches": ["prod-security-watch"]
    }
  }'

Exceptions have mandatory expiration dates — they force you to revisit the decision periodically rather than ignoring CVEs forever.

Summary: Xray vs Standalone Scanners

FeatureXrayTrivy/Grype
Scans on push (automatic)YesCI step required
Blocks download of violating artifactsYesNo
Transitive dependency analysisYesYes
License scanningYesLimited
Build-level scan historyYesNo
Policy enforcement without code changeYesPipeline changes needed
CostIncluded with Artifactory EnterpriseFree

Xray is most valuable when you've already standardized on Artifactory. The automatic scan-on-index and download-block capabilities close gaps that CI-only scanning leaves open — a developer pulling an old image directly from the registry bypasses your CI pipeline entirely but not Xray.

Share:

Was this article helpful?

Amara Okafor
Amara Okafor

DevSecOps Lead

Security-first mindset in everything I ship. From zero-trust architectures to supply chain security, I make sure your pipeline doesn't become your weakest link.

Related Articles

Discussion