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

# OpenCTI

> Import ChainPatrol threats into OpenCTI as indicators and observables, with lifecycle labels, scores, validity and decay, and investigation walkthroughs.

The ChainPatrol OpenCTI connector is an **external-import** connector for [OpenCTI](https://filigran.io/solutions/open-cti/). It imports the threats ChainPatrol investigates for your organization, and optionally the global blocklist, as indicators and observables your analysts can pivot on and your detection rules can threshold on.

It is a **pull** connector: you run it inside your network, against your own OpenCTI. ChainPatrol never needs a route into your infrastructure.

## Quick start

You need a running OpenCTI **7.261008.0** with Docker Compose, and a ChainPatrol API key. To install OpenCTI first, follow [Filigran's deployment guide](https://docs.opencti.io/latest/deployment/installation/).

**1. Create a ChainPatrol API key**

In the [ChainPatrol dashboard](https://app.chainpatrol.io/admin), go to **Settings → API Keys** and create a key.

**2. Add the connector to your OpenCTI docker-compose.yml**

Add this service next to your existing OpenCTI services:

```yaml theme={null}
  connector-chainpatrol:
    image: chainpatrol/opencti-connector:latest
    environment:
      - OPENCTI_URL=http://opencti:8080
      - OPENCTI_TOKEN=${OPENCTI_ADMIN_TOKEN}
      - CONNECTOR_ID=${CONNECTOR_CHAINPATROL_ID}   # any UUIDv4: run `uuidgen`
      - CONNECTOR_TYPE=EXTERNAL_IMPORT
      - CONNECTOR_NAME=ChainPatrol
      - CONNECTOR_SCOPE=chainpatrol
      - CONNECTOR_LOG_LEVEL=info
      - CONNECTOR_DURATION_PERIOD=PT1H
      - CHAINPATROL_API_KEY=${CHAINPATROL_API_KEY}
    read_only: true
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    restart: always
```

Then add `CONNECTOR_CHAINPATROL_ID` and `CHAINPATROL_API_KEY` to the `.env` file next to it.

**3. Start it**

```bash theme={null}
docker compose up -d connector-chainpatrol
docker compose logs -f connector-chainpatrol
```

Within a minute the log shows `Polling the organization threat feed`, then a summary such as `Organization threats: 120 fetched, 118 converted`. If a setting is wrong, the connector exits with a one-line message naming it, for example `ChainPatrol rejected CHAINPATROL_API_KEY (401)`.

**4. See your threats in OpenCTI**

The connector appears under **Integrations → Deployed**. Your threats appear under **Observations → Indicators**, each labelled with its ChainPatrol status. The first sync imports your full history; OpenCTI's workers can take several minutes to write it all, so scale them up for a large organization. After that the connector checks for changes every hour.

> [!TIP]
>
> The example uses the admin token for brevity. For anything beyond a trial, run the connector as its own OpenCTI user: create one under **Settings → Security → Users**, add it to a group with the Connector role (OpenCTI ships one called *Connectors*), and set `OPENCTI_TOKEN` to its API token. Every imported entity then shows that user as its creator instead of `admin`, so ChainPatrol data is easy to filter, and gets that group's confidence level instead of the admin's.

The rest of this page explains what the connector imports, how to configure it, and how to investigate with it in OpenCTI.

## What it imports

The connector has two feeds, configured independently. Most deployments only want the first.

### Your organization's threats (on by default)

Everything ChainPatrol has investigated on your behalf, **from the moment a report is filed**, not just once it is confirmed. Each asset carries labels describing where it is in its lifecycle:

| Label | Meaning |
| - | - |
| `chainpatrol:status:investigating` | A report was filed and is under review |
| `chainpatrol:status:escalated` | ChainPatrol passed it to your team for a decision |
| `chainpatrol:status:blocked` | Confirmed malicious and on the [blocklist](https://chainpatrol.com/docs/concepts/blocklist.md) |
| `chainpatrol:status:allowed` | Cleared as a false positive. The indicator is **revoked** |
| `chainpatrol:status:closed` | Reviewed, but neither blocked nor cleared |
| `chainpatrol:takedown:open` \| `submitted` \| `completed` \| `cancelled` \| `retracted` | [Takedown](https://chainpatrol.com/docs/concepts/takedowns.md) progress |
| `chainpatrol:target:<slug>` | Which of your [brands](https://chainpatrol.com/docs/concepts/brands.md) it impersonates |
| `chainpatrol:uptime:alive` \| `dead` \| `unknown` | Whether the asset was reachable at the last check |

The labels are on both the indicator and the observable. The label names are chosen so the status sorts first. OpenCTI orders an entity's labels alphabetically and its list views show only the first one, so the status is the label you see in every list.

> [!NOTE]
>
> Connector versions before October 2026 used `chainpatrol:brand:*` and `chainpatrol:liveness:*`. The first run after upgrading re-sends your organization's threats once and replaces the old labels as it goes.

This feed is marked **TLP:AMBER** by default, because it names your brands and your open investigations.

### The global blocklist (off by default)

Every asset on the ChainPatrol blocklist, roughly 500,000 entities, with **no organization context**: no brands, no reporting history, no takedown progress. Every asset is labelled `chainpatrol:status:blocked`.

This feed is marked **TLP:CLEAR** by default. It is off by default because the first run imports the whole blocklist.

> [!NOTE]
>
> If you enable both feeds, an asset that is on the global blocklist *and* was reported for your organization carries both TLP:CLEAR and TLP:AMBER. That is deliberate: the fact that it is blocked is public, but the investigation context around it is not.

## How each asset is modelled

Each asset becomes up to four kinds of OpenCTI object:

* **Indicator**: a STIX pattern such as `[url:value = 'https://…']`, carrying the labels, the score and, for cleared assets, `revoked: true`.
* **Observable**: the asset itself, carrying the same labels, score and summary as the indicator. The indicator is linked to it with a `based-on` relationship.
* **User-Account**: for social media profiles, the handle, linked to the observable with `related-to`.
* **Notes**: one per lifecycle transition (reported, escalated, blocked, takedown submitted…), each with a link to the asset's page in ChainPatrol.

| ChainPatrol asset type | Example | OpenCTI observable |
| - | - | - |
| `URL` | `evil.example` | Domain-Name |
| `PAGE` | `linktr.ee/evil` | Url |
| Social platforms (`TWITTER`, `TELEGRAM`, `DISCORD`, …) | `twitter.com/evil` | Url, plus User-Account for profiles |
| `EMAIL` | `scam@evil.example` | Email-Addr |
| `PHONE_NUMBER` | `+15550001111` | Phone-Number |

Crypto addresses (`ADDRESS`) and IP addresses are not imported yet.

The indicator and the observable both carry a one-line summary of the asset, for example *"Confirmed malicious and blocked by ChainPatrol on 2026-01-17. First reported 2026-01-17. Impersonates Jane Doe. Takedown completed. It was unreachable at the last check."*, and a link to the asset's page in ChainPatrol. Whichever one an analyst lands on first, they have the story.

## Validity and decay

An indicator's **valid from** date is the time of the sync that sent it, not the date the asset was reported or blocked. OpenCTI starts its decay clock at that date, so a historical one would make most of your history arrive already expired: OpenCTI would revoke a phishing site blocked a year ago that ChainPatrol still blocks. The real dates are kept as the indicator's **original creation date** and in its notes.

The connector only re-sends assets that changed, so it also **re-sends every blocked asset every 7 days** (`CHAINPATROL_REFRESH_INTERVAL_DAYS`). That restarts their decay before OpenCTI's shortest built-in lifetime runs out, so a blocked asset stays active for as long as ChainPatrol blocks it. Assets in any other state are left to decay normally.

The global blocklist is too large to refresh this way. Its entries decay unless ChainPatrol updates them. If that matters to you, add a decay exclusion rule for them in OpenCTI.

## Scores

Every indicator carries an `x_opencti_score` reflecting how sure ChainPatrol is, so you can build detection rules on a threshold. **Only blocked assets are marked for detection** (`x_opencti_detection: true`). An open report is a claim, not a finding.

| State | Default score | Marked for detection |
| - | - | - |
| investigating / closed | 25 | No |
| escalated | 50 | No |
| blocked | 90 | **Yes** |
| allowed | 0 | No, and the indicator is revoked |

You can change any of these scores. See [Configuration](#configuration).

> [!WARNING]
>
> OpenCTI's built-in indicator decay rules revoke indicators whose score falls to 20 or below, and lower every score over time. Keep open reports above 20, or open investigations will be revoked as soon as they arrive. URL indicators decay fastest under the default rules, so if you want open URL reports to stay active for long, raise their score or adjust your decay rules in OpenCTI under **Settings → Customization → Decay rules**.

## Configuration

Every setting is an environment variable. When running from source, the same settings can go in `config.yml` under `chainpatrol:`, using the lower-case name without the prefix (for example `CHAINPATROL_ORG_FEED_TLP` becomes `org_feed_tlp`). Environment variables win over the file.

| Variable | Default | Notes |
| - | - | - |
| `CHAINPATROL_API_KEY` | — | **Required** |
| `CHAINPATROL_ORGANIZATION_SLUG` | — | Only needed if your key can reach several organizations |
| `CHAINPATROL_ORG_FEED_ENABLED` | `true` | Your organization's threats |
| `CHAINPATROL_ORG_FEED_TLP` | `amber` | `clear`, `green`, `amber`, `amber+strict` or `red` |
| `CHAINPATROL_ORG_FEED_PAGE_SIZE` | `100` | 1–1000 |
| `CHAINPATROL_BLOCKLIST_FEED_ENABLED` | `false` | The global blocklist |
| `CHAINPATROL_BLOCKLIST_FEED_TLP` | `clear` | |
| `CHAINPATROL_BLOCKLIST_FEED_PAGE_SIZE` | `1000` | 1–1000 |
| `CHAINPATROL_POLL_OVERLAP_MINUTES` | `10` | See [Polling](#polling) |
| `CHAINPATROL_REFRESH_INTERVAL_DAYS` | `7` | How often every blocked asset is re-sent. See [Validity and decay](#validity-and-decay). `0` turns it off |
| `CHAINPATROL_SCORE_INVESTIGATING` | `25` | Score for each lifecycle state, 0–100 |
| `CHAINPATROL_SCORE_CLOSED` | `25` | |
| `CHAINPATROL_SCORE_ESCALATED` | `50` | |
| `CHAINPATROL_SCORE_BLOCKED` | `90` | Must be at least as high as every other score. Also used for the global blocklist |
| `CHAINPATROL_SCORE_ALLOWED` | `0` | Allowed assets are revoked whatever their score |
| `CONNECTOR_DURATION_PERIOD` | `PT1H` | Time between runs, as an ISO-8601 duration |

The connector checks its setup before it registers with OpenCTI. If something is wrong it exits with a one-line message naming the setting at fault, for example `ChainPatrol rejected CHAINPATROL_API_KEY (401)` or `OpenCTI rejected OPENCTI_TOKEN`. It checks that:

* `OPENCTI_URL`, `OPENCTI_TOKEN` and `CONNECTOR_ID` are set, and `CONNECTOR_ID` is a UUID;
* OpenCTI accepts `OPENCTI_TOKEN` and ChainPatrol accepts `CHAINPATROL_API_KEY`;
* at least one feed is enabled, every TLP, page size and score is in range, and no state scores higher than `blocked`.

Check the connector's logs with `docker compose logs connector-chainpatrol` if it does not appear in OpenCTI after a minute.

## Polling

The **first run is a full backfill**: every threat in your history is imported. Later runs only fetch what changed since the previous run.

Each run rewinds its starting point by `CHAINPATROL_POLL_OVERLAP_MINUTES` so that a change landing mid-run is never missed. Re-reading a few minutes is harmless, because every update is an upsert. If a run fails, the next one retries the same window instead of skipping it.

> [!TIP]
>
> A full backfill of a large organization can produce well over a hundred thousand OpenCTI queue messages. Scale up your OpenCTI workers for the first sync.

## Labels you add are safe

OpenCTI adds labels without removing old ones, so an asset that moved from `investigating` to `blocked` would otherwise carry both. Before each update the connector removes the outdated `chainpatrol:status:*`, `chainpatrol:takedown:*` and `chainpatrol:uptime:*` label. It never removes `chainpatrol:target:*` labels, since several brands can apply, and it never touches labels outside the `chainpatrol:` prefix, so your analysts' labels and other connectors' labels are left alone.

## Investigating in OpenCTI

Once the first sync has run, the examples below work on any OpenCTI instance with the connector attached. The screenshots use made-up example data for a fictional brand, Acme Exchange.

![Imported indicators, newest first, status label first](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/indicators-list.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=4e270a6fe231075274db1527c8393f07)

### Find what is still live

Open **Observations → Indicators**, add a **Label** filter for `chainpatrol:status:blocked`, and a second one for `chainpatrol:uptime:alive`. What is left is every confirmed phishing site, fake profile or scam page that was still reachable at ChainPatrol's last check. Those are the ones to block first or escalate. Save the filter to keep it as a standing work queue.

![Indicators filtered to blocked and still reachable](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/filter-blocked-alive.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=5cdf0e257cb0e0c5c776b53daf4c7b15)

The colour of the status label tells you the state at a glance: red for blocked, orange for escalated, yellow for investigating, grey for closed and green for allowed. On a narrow screen the label text can be cut off, but the colour still shows.

### Check a domain, URL or handle from an alert

Paste the value from a SIEM alert, a reported email or a support ticket into OpenCTI's global search. Every match shows its ChainPatrol status, and the **Analyses** column counts its lifecycle notes.

![Global search for a suspicious domain](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/global-search.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=0db6ef9a569e63330ccd8a435e188237)

Open the observable to see the one-line summary, the score, the labels and a link to the asset in ChainPatrol. You don't need to click through to the indicator.

![An observable with its ChainPatrol summary and labels](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/observable-overview.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=ef19e380b8f6c133e4afce714ff4d033)

### Follow a case from report to takedown

Open the indicator. Its **original creation date** is when ChainPatrol first saw the asset. **Valid from** is the sync that sent it, which keeps OpenCTI's decay running from today rather than from the first report. **Detection** is on only for blocked assets.

![A blocked indicator](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/indicator-overview.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=82b8e41fb494f554d869302c970d745b)

Scroll to **Notes about this entity** for the timeline, newest first: reported, escalated, blocked, takedown submitted and takedown completed. Each note links to the asset's page in ChainPatrol, where the reports, evidence and takedown history live.

![Lifecycle notes on an indicator](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/indicator-notes.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=04e857e2f96b7ad02c2e645ea4acd4fc)

### See everything impersonating one brand

Filter on `chainpatrol:target:<your-brand>` to scope a campaign. Brands and the people ChainPatrol protects for you, such as executives, each get their own label. Add a status or takedown label to narrow it further, for example everything targeting your brand that is still under investigation.

![Indicators targeting one brand](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/filter-target-brand.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=1af5428f52d73bfb59f9e87ed1361311)

### Track takedowns

`chainpatrol:takedown:completed`, `submitted`, `open` and `cancelled` show where each removal stands. Use the export button on a filtered list for your own reporting.

![Indicators with a completed takedown](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/filter-takedown-completed.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=3f033b0d15142873d8a25889a87b7bcd)

### Pivot from a social profile to the account

A social media profile becomes a URL observable linked to a **User-Account** holding the handle. Open the observable's **Knowledge** tab to reach it, and use the handle to correlate across your other sources.

![A social profile linked to its User-Account](https://mintcdn.com/chainpatrol/nBqvKdAP48i99MLH/images/opencti/social-user-account.webp?fit=max\&auto=format\&n=nBqvKdAP48i99MLH\&q=85\&s=658354b3cdfcfda6a0a597576ed3b437)

### Build detection on confirmed threats only

Point detection rules and exports at indicators with **Detection = Yes**, or a score of at least 90. That is exactly the blocked set. Open reports (score 25) and cleared false positives (score 0, revoked) stay out of your blocklists.

## Support

For help with the connector, your data or your API key, contact [support@chainpatrol.io](mailto:support@chainpatrol.io).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.