dbt CTI Pipeline¶
For Hiring Managers — Detection / Threat-Intel / Data Engineering
TL;DR: I built a working data pipeline that ingests live cyber threat intelligence feeds and transforms them into behavioral-analytics tables using dbt Core + DuckDB. Staging to intermediate to mart models, 6 models with 14 passing tests, documentation, and a full lineage DAG.
What I bring:
- dbt Core modeling (staging to mart layering, tests, docs, DAG)
- SQL transformation and data modeling over messy external feeds
- Multi-source correlation (unioning independent CTI feeds)
- Anomaly-ready time-series (daily volume vs. trailing baseline)
- Python ingestion of live no-auth threat feeds into an analytical store
Why this matters: this is threat-intel data modeling in miniature — the same ingest, transform, test, and analyze pattern a CTI enrichment pipeline uses, built end-to-end and reproducible from a single command.
What it does¶
6 dbt Models Staging to intermediate to behavioral-analytics marts, layered and modular
14 Passing Tests not_null, unique, and accepted_values tests across staging and marts
2 Live CTI Feeds abuse.ch URLhaus + ThreatFox, ingested no-auth into a local analytical store
Full Lineage DAG Auto-generated docs and dependency graph via dbt docs
The pipeline ingests live threat intelligence, normalizes it into a common schema, unions independent feeds for multi-source correlation, and produces analysis-ready tables — IOC volume by threat family, daily volume against a trailing baseline (anomaly-ready), and cross-feed corroborated indicators.
Architecture¶
%%{init: {"themeVariables": {"fontSize": "18px"}}}%%
flowchart LR
subgraph SOURCES["🌐 LIVE CTI FEEDS"]
direction TB
S1["abuse.ch<br/>URLhaus"] ~~~ S2["abuse.ch<br/>ThreatFox"]
end
subgraph RAW["🗄️ DuckDB (raw)"]
R1["raw tables<br/>(Python ingest)"]
end
subgraph STG["🧹 STAGING (dbt)"]
direction TB
G1["stg_urlhaus"] ~~~ G2["stg_threatfox"]
end
subgraph INT["🔗 INTERMEDIATE"]
I1["int_iocs_unioned<br/>(multi-source correlation)"]
end
subgraph MART["📊 BEHAVIORAL-ANALYTICS MARTS"]
direction TB
M1["ioc_by_family"] ~~~ M2["daily_ioc_volume<br/>(anomaly-ready)"] ~~~ M3["cross_feed_iocs"]
end
SOURCES --> RAW --> STG --> INT --> MART
style SOURCES fill:#fef3c7,stroke:#d97706,stroke-width:2px
style RAW fill:#e5e7eb,stroke:#6b7280,stroke-width:2px
style STG fill:#dbeafe,stroke:#2563eb,stroke-width:2px
style INT fill:#dcfce7,stroke:#16a34a,stroke-width:2px
style MART fill:#f3e8ff,stroke:#9333ea,stroke-width:2px The layering follows standard dbt practice: raw data stays 1:1 with the source, staging cleans and types it into a common schema, the intermediate layer unions the feeds, and the marts are the analysis-ready tables.
The behavioral-analytics marts¶
| Mart | What it answers | Signal |
|---|---|---|
| IOC by threat family | Which malware families are most active, and how long have we seen them? | Volume + time span per family (Cobalt Strike, Remcos, AsyncRAT, Mirai, and more in a live run) |
| Daily IOC volume | Is today abnormal versus recent history? | Daily counts against a 7-day trailing baseline — a spike above baseline is a candidate anomaly |
| Cross-feed IOCs | Which indicators are corroborated by more than one source? | IOCs independently reported by both feeds — a higher-confidence lead than a single sighting |
The cross-feed mart is the multi-source-correlation payoff: an indicator seen in both URLhaus and ThreatFox is a stronger lead than one seen once.
Engineering notes¶
- Stack: dbt Core (dbt-duckdb adapter) for transformation, testing, docs, and DAG; DuckDB as a local, zero-config analytical database; Python (
requests) for feed ingestion. - Tested, not asserted: every model carries schema tests —
not_nullanduniqueon indicator keys,accepted_valueson IOC types and source feeds — so a broken transform fails the build rather than shipping bad data. - Reproducible from one command: ingest the live feeds, then
dbt runanddbt testbuild and validate the whole pipeline;dbt docsgenerates the lineage graph. - Honest data modeling: the models surface real feed characteristics rather than hiding them — for example, URLhaus reports a coarse threat category where ThreatFox supplies named malware families, and the marts keep that visible rather than filtering it away.
Skills demonstrated¶
| Category | Skills |
|---|---|
| Data Engineering | dbt Core modeling, staging-to-mart layering, schema tests, lineage/DAG, DuckDB |
| Threat Intelligence | CTI feed ingestion (URLhaus, ThreatFox), IOC normalization, multi-source correlation |
| SQL | Transformation models, unions across sources, window functions for baselines |
| Python | Live feed ingestion, schema handling, loading into an analytical store |
| Data Quality | Test-driven models, honest handling of source-data quirks |
Related projects¶
- dbt-cti-pipeline — Full repo with models, tests, and lineage DAG
- Detection Engineering — Authored Sigma detections and IR workflows
- GIAP™ — Automation platform demonstrating similar pipeline thinking applied to GRC
Want to discuss threat-intel data engineering?
I'm actively seeking roles where I can build and secure the data pipelines behind threat detection and intelligence.