Configure OpenCTI integration¶
In progress
Native OpenCTI pull, push, and scheduled synchronization are not available in ZettelForge 2.8.0. ThreatRecall.ai supports manual STIX 2.1 exchange and a generic, manually triggered TAXII 2.1 pull. This audit did not connect the TAXII path to a live OpenCTI server, so OpenCTI-specific TAXII compatibility remains in progress.
Use this guide to choose a working handoff path today. You can exchange STIX bundles with ThreatRecall.ai, stage a compatible TAXII collection in ThreatRecall.ai, or write relationships from your own connector into ZettelForge.
Prerequisites¶
- For the ZettelForge path, install ZettelForge 2.8.0 and complete the Quickstart.
- For the ThreatRecall.ai paths, use a plan with direct ingestion and create an ingest-scoped API key. A read-capable key is sufficient for export.
- Export a STIX 2.1 bundle from OpenCTI, or obtain a compatible TAXII 2.1 collection objects URL from your OpenCTI operator.
- Keep OpenCTI and ThreatRecall credentials out of source-controlled files.
Choose a path¶
| Goal | Product and path | State |
|---|---|---|
| Move a bundle from OpenCTI into managed memory | ThreatRecall.ai STIX import | Verified in the SaaS route tests |
| Move a recall session back to OpenCTI | ThreatRecall.ai STIX export | Implemented with read, TLP, lifecycle, and redistribution gates |
| Pull a TAXII collection into a review batch | ThreatRecall.ai TAXII ingestion | Runtime and safety controls verified; live OpenCTI compatibility unverified |
| Feed selected relationships into local memory | ZettelForge ingest_relationship() |
Verified in ZettelForge 2.8.0 |
| Run native or scheduled OpenCTI synchronization | Neither product | In progress |
Exchange a STIX bundle through ThreatRecall.ai¶
- Export a STIX 2.1 bundle from OpenCTI.
- Submit the bundle to
POST https://app.threatrecall.ai/api/ingest/stix. Send your ingest-scoped key inX-API-Keyand wrap the STIX object in the JSON fieldbundle. A valid bundle returned HTTP201in the route test; a read-scoped key returned HTTP403before ingestion. - Run recall in ThreatRecall.ai and retain the returned
recall_id. - Request
GET https://app.threatrecall.ai/api/recall/sessions/{recall_id}/export.stixwith a read-capable key. - Import the returned STIX 2.1 bundle into OpenCTI through the import path your OpenCTI operator supports.
The export route returns application/stix+json;version=2.1 after its policy
checks pass. It denies export when source redistribution rules block an object.
It also requires an explicit workspace grant when the remaining export contains
TLP:RED data. These gates prevent a technically valid STIX handoff from
bypassing sharing restrictions.
Stage a TAXII collection in ThreatRecall.ai¶
ThreatRecall.ai contains a generic TAXII 2.1 pull runtime. It does not contain an OpenCTI-specific adapter or a scheduler.
- Ask your ThreatRecall operator to allowlist the TAXII host with
TAXII_ALLOWED_HOSTS. - Register the feed with
POST /api/threat-intel/feeds. Store the collection objects URL inconnector_config_refand set an explicit default TLP and redistribution state. - Trigger
POST /api/threat-intel/batcheswithinput_kindset totaxii_2_1_pulland the registeredfeed_source_id. - Review the staged items, then commit the approved item identifiers through
POST /api/threat-intel/batches/{batch_id}/commit.
The pull requires HTTPS outside local development, rejects hosts that are not allowlisted, revalidates redirect destinations, blocks private or reserved addresses, applies an eight-second default timeout, and caps a response at 512 KiB. The service stages returned STIX objects for review; it does not silently commit them.
OpenCTI compatibility boundary
This run verified the TAXII client and staging code with local fixtures. It
did not authenticate to a live OpenCTI collection. The current client sends
an Accept header but no API token or other authorization credential, so an
authenticated OpenCTI TAXII collection is unsupported. Confirm the
collection URL and returned STIX objects in your environment before you
depend on this path.
Write relationships into ZettelForge¶
Use ingest_relationship() when your connector already reads OpenCTI data and
you want to add selected edges to the local ZettelForge knowledge graph.
from zettelforge import MemoryManager
mm = MemoryManager()
mm.ingest_relationship(
from_type="actor",
from_value="apt28",
to_type="tool",
to_value="mimikatz",
relationship="USES_TOOL",
properties={"confidence": 0.85, "source": "opencti-export"},
)
relationships = mm.get_entity_relationships("actor", "apt28")
print(len(relationships))
print(relationships[0]["relationship"])
print(relationships[0]["node"]["entity_value"])
Observed output:
1
USES_TOOL
mimikatz
Use canonical lowercase entity values. The lookup path canonicalizes the query
before it reads the graph; the audit found that display-case values such as
APT28 did not round-trip through this lookup. Repeating the same source,
target, and relationship updates the edge properties and keeps one edge.
This method writes a relationship only. Your connector remains responsible for reading OpenCTI, selecting and normalizing objects, handling checkpoints, and scheduling later runs.
Understand the ZettelForge configuration keys¶
ZettelForge 2.8.0 parses this block from config.yaml:
opencti:
url: http://localhost:8080
token: ""
sync_interval: 0
The matching environment variables are OPENCTI_URL, OPENCTI_TOKEN, and
OPENCTI_SYNC_INTERVAL. The defaults are http://localhost:8080, an empty
token, and 0 seconds.
These keys do not activate a connector in ZettelForge 2.8.0. Setting a nonzero interval does not start a worker because the release contains no native OpenCTI connector module. Keep the token blank in the YAML file and provide it through the environment if your own connector reads this configuration.
Related¶
- Quickstart: verify ZettelForge before you add a connector
- First CTI report: ingest and recall threat data
- Two products, one core: choose between ZettelForge and ThreatRecall.ai