Architecture
Crate graph
Five focused crates with acyclic edges; each lower crate is usable standalone. Four bindings wrap one shared FFI facade:
graph TD
subgraph core [Rust core]
P[stix-pattern<br/>lexer + parser] --> U[stix<br/>umbrella]
M[stix-model<br/>objects, bundles, registry] --> U
P --> X[stix-matcher<br/>engine]
M --> X
X --> U
U --> F[stix-ffi<br/>facade]
end
subgraph bindings [Language bindings]
F --> PY[python<br/>PyO3]
F --> JV[java<br/>jni-rs]
F --> TN[ts-node<br/>napi-rs]
F --> TW[ts-wasm<br/>wasm-bindgen]
end
Data flow
flowchart LR
PS[pattern string] -->|stix-pattern::parse| AST[Pattern AST]
BJ[bundle JSON] -->|ModelRegistry::parse_bundle| B[Bundle]
B --> OS[ObjectStore<br/>id → object]
AST --> MX{{stix-matcher}}
B --> MX
OS -->|deref _ref paths| MX
MX --> MR[MatchResult<br/>matched + observation indices]
Custom-type hooks run inside parse_bundle (once per object, synchronously); their
output is stored as data, so nothing re-enters host-language code during matching.
One match, step by step
Pattern: [network-traffic:src_ref.value = '198.51.100.5'] against a bundle
containing an ipv4-addr, a network-traffic whose src_ref points at it, and an
observed-data referencing both.
- Observations.
match_bundlefinds theobserved-dataSDO → one observation containing the two SCOs, and builds anObjectStoreover the bundle. - Candidates. The expression references one type,
network-traffic; the observation has one candidate → one binding to try. - Path resolution.
src_refresolves on the bound object to the string"ipv4-addr--a1"; because the path continues (.value), the matcher treats it as an id, dereferences it through the store, and readsvalueoff theipv4-addr→"198.51.100.5". - Operator.
=compares the resolved value with the literal → true. - Result. The observation satisfies the block →
MatchResultwithmatched = trueand that observation’s index.
The FFI facade
stix-ffi is a pure-Rust crate (no FFI macros) that every binding wraps: an
Engine handle owning the registry, opaque Pattern/Bundle handles, a plain
MatchOutcome, and a flat FfiError { code, message } each language maps onto its
own exception hierarchy. Deep structure crosses as JSON; each binding converts it to
native objects at its edge. This keeps all four bindings thin and behaviorally
identical.
How the repo is run
The repository is organized into agent-owned areas (core, one per binding) with an
ownership map and issue workflow in
AGENTS.md;
design specs and implementation plans live under docs/superpowers/.