> ## Documentation Index
> Fetch the complete documentation index at: https://docs.safedep.io/llms.txt
> Use this file to discover all available pages before exploring further.

# JFrog Xray Integration

> Stream SafeDep malware intelligence to JFrog Xray

<Warning>
  The SafeDep JFrog Xray integration is available with the Threat Intel Feed add-on. To enable the add-on, see the [pricing page](https://safedep.io/pricing/#threat-intel).
</Warning>

The JFrog Xray integration runs as a daemon. It polls SafeDep for verified malicious packages
and pushes them to JFrog Xray as Custom Issues. With a blocking policy in place, Xray blocks
those packages for every developer on that instance.

<Tip>
  To preview the feed without JFrog credentials, run it with [`--dry-run`](#dry-run) first.
</Tip>

## Prerequisites

* SafeDep CLI installed ([install](https://github.com/safedep/cli#safedep-cli))
* JFrog instance with Xray enabled
* JFrog Xray scoped [Access Token](https://docs.jfrog.com/administration/docs/access-tokens)
* *Optional*, for blocking malicious packages on developer machines or CIs
  * JFrog Xray Malware security `policy` and repository `watch` with a block action configured

## How It Works

<Steps>
  <Step title="Authenticate with SafeDep">
    ```bash theme={null}
    # Option 1: API key login (--api-key is a flag; pass the value with --api-key-value)
    safedep auth login --tenant your-tenant.safedep.io --api-key --api-key-value YOUR_API_KEY

    # Option 2: Environment variables (read directly by safedep; no login command needed)
    export SAFEDEP_TENANT_ID=your-tenant.safedep.io
    export SAFEDEP_API_KEY=YOUR_API_KEY
    ```
  </Step>

  <Step title="Start the integration daemon">
    ```bash theme={null}
    safedep integration jfrog run \
      --instance-url https://yourcompany.jfrog.io \
      --instance-access-token YOUR_JFROG_TOKEN

    # or pass token via environment variable (see Environment Variables below)
    ```

    The daemon polls SafeDep continuously (default: every 5 minutes) and pushes any newly
    verified malicious packages to Xray.

    On the first run the daemon starts from the current time and does not pull older reports.
    To seed history, pass `--backfill` with a duration: `--backfill 168h` covers the last 7 days.
    Later runs resume from the saved cursor, so `--backfill` is ignored after the first run.

    ```text Output theme={null}
    i Validating JFrog connectivity
    ✓ JFrog connectivity OK (URL + token verified)
    i Starting JFrog Syncing with SafeDep Threat Intel Feed
    i No saved cursor: starting fresh from now
    ✓ Pushed: @hideliar/9router (npm) versions: 0.4.25
    i   JFrog: SD-01KR3WJYFTSNZFFS5CFNYVGZFH [201]
    ✓ Pushed: @sheason/d-pi (npm) versions: 0.4.3
    i   JFrog: SD-01KR3XSCZ0WNAEYY6CNMD4CQH3 [201]
    i Feed cycle complete at 2026-08-25T10:15:04Z, next in 5m0s
    ...
    ..
    .
    ```
  </Step>

  <Step title="Verify Blocking via JFrog">
    <Info>
      Assumes a Malicious Package blocking policy and watch configured for the target repository (e.g. `npm-remote`).
    </Info>

    ```bash theme={null}
    jf npm install <Malicious Ingested Package>
    ```

    `jf npm install` triggers Xray to index `<Malicious Ingested Package>`. Xray raises a policy violation and blocks the download.

    ```text Install Output theme={null}
      npm ERR! 403 on a server you do not have access to.
      {
        "error": {
          "code": "E403",
          "summary": "403 Forbidden - GET https://yourcompany.jfrog.io/artifactory/api/npm/npm-virtual/<Malicious Ingested Package>",
          "detail": "In most cases, you or one of your dependencies are requesting\na package version that is forbidden by your security policy, or\non a server you do not have access to."
        }
      }
    ```
  </Step>
</Steps>

## <Icon icon="triangle-exclamation" color="#f59e0b" /> Limitations

#### Malicious Packages Blocking

`npm` and other package managers cache packages locally on developer machines. If a developer installs a package before SafeDep flags it, the cached copy remains accessible even after the integration pushes it to Xray.

Running `npm cache clean --force` removes the cached copy, but it clears the entire local cache and forces a full re-download of all packages.

```bash theme={null}
npm cache clean --force
```

## Configuration

### CLI Flags

| Flag                      | Required | Default     | Description                                                                                         |
| ------------------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------- |
| `--instance-url`          | Yes\*    | None        | JFrog instance base URL. Must use `https://`.                                                       |
| `--instance-access-token` | Yes\*    | None        | JFrog access token scoped to Xray.                                                                  |
| `--poll-interval`         | No       | `5m`        | Duration between poll cycles (`30s`, `5m`, `1h`).                                                   |
| `--backfill`              | No       | `0`         | First-run window to seed history (e.g. `24h`, `168h`). `0` starts from now.                         |
| `--dry-run`               | No       | `false`     | Preview the feed and print what would be pushed, without sending to JFrog. See [Dry run](#dry-run). |
| `--profile`               | No       | `"default"` | SafeDep credential profile (inherited from root).                                                   |

\*Required unless the corresponding environment variable is set.

### Environment Variables

For server deployments or CI pipelines, use environment variables to avoid passing secrets as CLI flags. When both are set, flags take precedence.

| Variable                                             | Corresponding Flag        |
| ---------------------------------------------------- | ------------------------- |
| `SAFEDEP_INTEGRATION_JFROG_ARTIFACTORY_URL`          | `--instance-url`          |
| `SAFEDEP_INTEGRATION_JFROG_ARTIFACTORY_ACCESS_TOKEN` | `--instance-access-token` |

**Example: environment variable setup**

```bash theme={null}
export SAFEDEP_INTEGRATION_JFROG_ARTIFACTORY_URL=https://yourcompany.jfrog.io
export SAFEDEP_INTEGRATION_JFROG_ARTIFACTORY_ACCESS_TOKEN=***
safedep integration jfrog run
```

## Dry run

Use `--dry-run` to test the feed without a JFrog connection.

<Tip>
  `--dry-run` previews the feed and prints each finding as a `Would push:` line, without sending to JFrog. It needs no JFrog credentials, so you can check the feed before you connect an instance.

  ```bash theme={null}
  # Preview the last 24 hours, then clear the cursor before a real run
  safedep integration jfrog run --dry-run --backfill 24h
  safedep integration jfrog cursor remove
  ```
</Tip>

A dry-run does everything a real run does, except the final step: it prints each finding instead of sending it to JFrog.

<Warning>
  Dry-run advances the same saved cursor. Run [`cursor remove`](#clear-the-cursor) before the first real run, or that run skips what the preview consumed.
</Warning>

## Manage the feed cursor

The `run` command saves a cursor so it resumes where it stopped. The cursor is per SafeDep profile, so a change affects only the profile you select with `--profile`.

### Move the cursor

Set the cursor to an [RFC3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp. The next run processes reports updated after this time.

```bash theme={null}
safedep integration jfrog cursor set 2026-08-01T00:00:00Z
```

### Clear the cursor

Remove the saved cursor. The next run starts fresh from the current time, or from the `--backfill` window.

```bash theme={null}
safedep integration jfrog cursor remove
```
