Skip to content

STIX alignment in ZettelForge

ZettelForge 2.8.0 is a CTI memory system, not a STIX 2.1 object store. It extracts STIX-adjacent labels from unstructured text and stores them as note metadata and graph entities. ThreatRecall.ai is the separate hosted SaaS and owns native STIX bundle interchange.

This boundary lets you capture intelligence quickly without mistaking a ZettelForge note for a standards-conformant STIX object.

Prerequisites: None. Use the STIX schema reference for exact labels and fields, and the KG edge schema for stored relationship shapes.

The mental model

The OASIS STIX 2.1 specification separates threat intelligence into domain objects (SDOs), cyber observable objects (SCOs), and relationship objects (SROs). ZettelForge borrows that vocabulary at three lighter-weight layers:

  1. A memory note keeps the source text and editorial metadata.
  2. The extractor attaches typed strings such as intrusion_set, cve, ipv4, and attack_pattern.
  3. The graph connects those strings with labels such as USES_TOOL, EXPLOITS_CVE, and MENTIONED_IN.

The labels preserve enough structure for CTI retrieval. They do not turn the note, entity, or edge into a STIX object.

Where the vocabulary aligns

The released extractor recognizes 19 labels:

Layer ZettelForge labels STIX relationship
CTI subjects cve, intrusion_set, actor, tool, campaign, attack_pattern SDO-adjacent subjects
Observables ipv4, domain, url, md5, sha1, sha256, email SCO-adjacent values
Conversational context person, location, organization, event, activity, temporal ZettelForge context, not STIX objects

The distinction between intrusion_set and actor is operational rather than fully ontological. The deterministic extractor assigns APT, UNC, TA, FIN, and TEMP designations to intrusion_set; it assigns a short built-in list of named groups to actor. recall_actor() searches actor, the compatibility label threat_actor, and intrusion_set so older and newer notes remain findable.

This is alignment, not conformance. A conformant STIX object also carries an object type, a STIX identifier, a specification version, and the common fields required for that object. ZettelForge's extracted strings carry none of that object envelope.

Metadata preserves analyst judgments

Each MemoryNote has fields that let an integration retain STIX-adjacent judgments without adopting the STIX data model:

Field Released default Purpose
metadata.stix_confidence -1 Holds an integer confidence supplied by your integration; -1 means unset.
metadata.tier "B" Records ZettelForge's editorial tier: authoritative, operational, or support.
metadata.tlp "" Stores the marking text supplied by a caller.
metadata.vuln None Holds optional CVSS, EPSS, and CISA KEV fields for vulnerability notes.

The released Pydantic models store these values but do not enforce their documented numeric ranges. Your importer remains responsible for validating a STIX confidence value before assigning it.

TLP enforcement is in progress

ZettelForge 2.8.0 stores metadata.tlp as an unrestricted string. The released package does not normalize TLP values or apply a TLP ceiling in core recall and snapshot paths. Enforce sharing policy in the calling application. ThreatRecall.ai applies its own tenant-scoped controls in the hosted service.

The separation between stix_confidence and tier is deliberate. A numeric score describes confidence in one assertion. A tier describes the editorial role of the source. Keeping both prevents an authoritative advisory and an unverified report from becoming indistinguishable just because they carry the same number.

Relationships stay flexible

ZettelForge writes graph nodes and edges to SQLite and mirrors explicit ingest_relationship() writes to its legacy JSONL graph. The SQLite edge key uses the source node, target node, relationship label, and optional note_id provenance. Repeating the same key updates its properties.

MemoryManager does not validate relationship labels against a STIX schema. That flexibility is useful when raw reports describe organization-specific relationships, but it also means an edge named USES_TOOL is not a STIX Relationship Object. The separate ontology validator exposes a stricter vocabulary, yet the v2.8.0 release does not wire it into MemoryManager writes.

Why ZettelForge stops short of native STIX

ZettelForge accepts conversational notes, reports, and observations as raw text. Its deterministic extractor can identify a CVE, ATT&CK technique, threat group, tool, or observable without first constructing a complete bundle. That keeps the ingestion path useful for material that never arrives as STIX JSON.

A native STIX store solves a different problem: preserving object identifiers, required properties, markings, relationship endpoints, and bundle round trips. ZettelForge 2.8.0 does not parse or emit STIX bundles, generate STIX object IDs, or validate SDOs, SCOs, and SROs.

The release also contains legacy hooks that look for an external TypeDB adapter, but the open-source distribution does not ship that adapter or its driver. The default graph path remains SQLite with a JSONL compatibility mirror.

TypeDB integration is in progress

Treat the TypeDB loader hooks as compatibility code, not as a shipped ZettelForge backend. The released adapter tests skip when the absent adapter cannot be imported.

The two-product boundary

Use ZettelForge when you want self-hosted memory and retrieval over raw CTI text. Build custom integration code when you need to translate its notes and edges into another platform's STIX model.

Use ThreatRecall.ai when you need hosted STIX interchange. Its current OpenAPI surface exposes authenticated STIX import at /api/ingest/stix and STIX export from recall sessions and threat-agent graph workflows. The service maps those objects to tenant-scoped PostgreSQL kg_nodes and kg_edges records.

That product boundary is the central design choice: ZettelForge optimizes the path from unstructured evidence to recallable memory; ThreatRecall.ai owns the standards-conformant interchange surface.