Get the raw IOCs
Four ways to pull the feed itself. Everything below is free, unauthenticated and CC0.
Every window and IOC type as JSON. No key, no signup, no per-caller throttling.
Windows: today · week · month · yearRefresh: 15 min
All of today's IOCs as JSON:
curl -s https://api.tweetfeed.live/v1/today | jq .
Filter by IOC type (url, domain, ip, sha256, md5):
curl -s https://api.tweetfeed.live/v1/today/url
Combine type and tag - this week's Cobalt Strike IPs:
curl -s https://api.tweetfeed.live/v1/week/ip/CobaltStrike
Response shape, one IOC per array element:
{
"date": "2026-05-03 06:07:00",
"user": "PhishStats",
"type": "domain",
"value": "evil.example.shop",
"tags": ["#phishing"],
"tweet": "https://x.com/PhishStats/status/..."
}
Full API referenceOpenAPI spec
The same four windows as CSV, straight off the data repo. Header: date,user,type,value,tags,tweet.
Windows: today · week · month · year
wget https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/today.csv
wget https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv
wget https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/month.csv
Raw feeds
Plain-text blocklists
#
Seven ready-to-consume exports of the last 30 days, one indicator per line, for Pi-hole, AdGuard Home, hosts files, dnsmasq, BIND RPZ and firewalls.
Window: 30 daysRefresh: 15 min
# Seven plain-text exports, rolling 30-day window, one indicator per line
curl -sfL https://api.tweetfeed.live/v1/blocklist/domains.txt # bare domains (Pi-hole)
curl -sfL https://api.tweetfeed.live/v1/blocklist/hosts.txt # 0.0.0.0 hosts file
curl -sfL https://api.tweetfeed.live/v1/blocklist/adguard.txt # AdGuard Home
curl -sfL https://api.tweetfeed.live/v1/blocklist/dnsmasq.txt # dnsmasq address= rules
curl -sfL https://api.tweetfeed.live/v1/blocklist/rpz.txt # BIND RPZ zone
curl -sfL https://api.tweetfeed.live/v1/blocklist/ips.txt # bare IPs
curl -sfL https://api.tweetfeed.live/v1/blocklist/urls.txt # full URLs, not just hosts
urls.txt is the exception: it carries the full URL rather than the host, so it also covers IOCs sitting on shared infrastructure that host-level blocking cannot reach without collateral damage. These files are a 1:1 mirror of the feed with no extra quality gate - pair them with an allowlist.
Bad domains listBad IPs list
Terminal & shell
Recipes you can paste into a shell right now. All of them return clean stdout, ready to pipe.
Terminal
Daily blocklist refresh
#
Rewrites a flat file every 15 minutes, ready for a firewall or proxy to reload.
Window: todayRefresh: 15 min
*/15 * * * * curl -sfL https://api.tweetfeed.live/v1/today/url \
| jq -r '.[].value' | sort -u > /etc/blocklists/tweetfeed-urls.txt
Terminal
Slice by malware family
#
Only the IPs tagged Cobalt Strike this week. Swap the tag for any of the tracked hashtags.
Window: week
curl -sL https://api.tweetfeed.live/v1/week/ip/CobaltStrike | jq -r '.[].value'
Tag index
Terminal
Pivot from a researcher
#
Every IOC published in the last 30 days by one handle, filtered server-side: a researcher handle is a valid path filter as long as you keep the leading @.
Window: month
curl -sL https://api.tweetfeed.live/v1/month/@malwrhunterteam \
| jq -r '.[] | "\(.type)\t\(.value)\t\(.tags|join(","))"'
Researchers
Terminal
IOC to source tweet
#
Every IOC carries provenance. Resolve one back to the post it came from before you alert on it.
Window: today
curl -sL https://api.tweetfeed.live/v1/today \
| jq -r '.[] | select(.value=="evil.example.com") | .tweet'
Terminal
Active campaigns
#
The daily clustering run, ranked by how many IOCs each campaign produced in the last 7 days.
Window: 30 daysRefresh: daily
curl -sL https://api.tweetfeed.live/v1/campaigns \
| jq -r '.campaigns[] | select(.ioc_count_7d > 0)
| "\(.ioc_count_7d)\t\(.confidence)\t\(.name)"' | sort -rn
Campaigns
Splunk
Keep a TweetFeed lookup table current, then join it against your indexes.
Splunk
Refresh the lookup table
#
A saved search that keeps the TweetFeed lookup current, in savedsearches.conf. Every query below reads from it.
Refresh: 15 min
[TweetFeed_today_lookup_refresh]
search = | inputlookup append=t external_lookup tweetfeed_today.csv \
| outputlookup tweetfeed_today.csv
cron_schedule = */15 * * * *
dispatch.earliest_time = -15m
enableSched = 1
Splunk
Match proxy logs against URLs and domains
#
A subsearch turns the lookup into a filter, then groups the hits by source host so you see who reached what.
Window: today
index=proxy [
| inputlookup tweetfeed_today.csv
| where type IN ("url","domain")
| rename value AS url
| fields url
] | stats count, values(tags), values(tweet) by src_ip, url
Splunk
DNS query enrichment
#
Any DNS query in the last 7 days that resolved a TweetFeed domain, carrying the tags and the source tweet along for triage.
Window: week
index=dns sourcetype=dns:query [
| inputlookup tweetfeed_week.csv
| where type="domain" | rename value AS query | fields query
] | table _time, src_ip, query, [tweetfeed.tags], [tweetfeed.tweet]
Save this one as a Notable Event with severity=medium and trigger it as soon as count > 0 to turn it into a real-time alert.
Microsoft Sentinel
No connector needed: externaldata() reads the CSV straight off the data repo at query time.
Sentinel
DNS hits with externaldata()
#
Reads today.csv at query time - no connector, no ingestion cost - and joins it against DnsEvents.
Window: todayLookback: 24h
let TweetFeedDomains = externaldata(date:string, user:string, type:string, value:string, tags:string, tweet:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/today.csv"]
with (format="csv")
| where type == "domain"
| project Domain = value, Tags = tags, Tweet = tweet;
DnsEvents
| where TimeGenerated > ago(24h)
| where Name in (TweetFeedDomains | project Domain)
| join kind=leftouter TweetFeedDomains on $left.Name == $right.Domain
| project TimeGenerated, Computer, ClientIP, Name, Tags, Tweet
Sentinel
Logon sources against TweetFeed IPs
#
The same pattern over week.csv and SecurityEvent, to catch authentication from a flagged address.
Window: weekLookback: 7d
let TweetFeedIPs = externaldata(date:string, user:string, type:string, value:string, tags:string, tweet:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv"]
with (format="csv")
| where type == "ip"
| project IPAddress = value, Tags = tags, Tweet = tweet;
SecurityEvent
| where TimeGenerated > ago(7d)
| where IpAddress in (TweetFeedIPs | project IPAddress)
| join kind=leftouter TweetFeedIPs on $left.IpAddress == $right.IPAddress
| project TimeGenerated, Computer, Account, IpAddress, Tags, Tweet
Microsoft Defender for Endpoint
Advanced hunting queries covering all four IOC types. Swap week.csv for today.csv or month.csv depending on your freshness-versus-coverage trade-off.
MDE advanced hunting docs
Defender
URLs and domains
#
Joins the URL and domain rows against DeviceNetworkEvents. The domain_whitelist array at the top is where your trusted assets go.
Window: weekLookback: 30d
let MaxAge = ago(30d);
let domain_whitelist = pack_array(
'XXX' // Some URL/Domain you want to whitelist.
);
let TweetFeed = materialize (
(externaldata(report:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv"]
with (format = "txt"))
| extend report = parse_csv(report)
| extend Type = tostring(report[2])
| where Type in('url','domain')
| extend RemoteUrl = tostring(report[3])
| where RemoteUrl !in(domain_whitelist)
| extend Tag = tostring(report[4])
| extend Tweet = tostring(report[5])
| project RemoteUrl, Tag, Tweet
);
union (
TweetFeed
| join (
DeviceNetworkEvents
| where Timestamp> MaxAge
) on RemoteUrl
) | project Timestamp, DeviceName, RemoteUrl, Tag, Tweet
Same join over the IP rows, with private ranges dropped by ipv4_is_private() so RFC1918 noise never fires.
Window: weekLookback: 30d
let MaxAge = ago(30d);
let IPaddress_whitelist = pack_array(
'XXX' // Some IP address you want to whitelist.
);
let TweetFeed = materialize (
(externaldata(report:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv"]
with (format = "txt"))
| extend report = parse_csv(report)
| extend Type = tostring(report[2])
| where Type == 'ip'
| extend RemoteIP = tostring(report[3])
| where RemoteIP !in(IPaddress_whitelist)
| where not(ipv4_is_private(RemoteIP))
| extend Tag = tostring(report[4])
| extend Tweet = tostring(report[5])
| project RemoteIP, Tag, Tweet
);
union (
TweetFeed
| join (
DeviceNetworkEvents
| where Timestamp> MaxAge
) on RemoteIP
) | project Timestamp, DeviceName, RemoteIP, Tag, Tweet
Defender
SHA-256 hashes
#
Unions process, file and image-load events, so a flagged hash is caught whether it ran, landed on disk or was loaded into another process.
Window: weekLookback: 30d
let MaxAge = ago(30d);
let SHA256_whitelist = pack_array(
'XXX' // Some SHA256 hash to whitelist.
);
let TweetFeed = materialize (
(externaldata(report:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv"]
with (format = "txt"))
| extend report = parse_csv(report)
| extend Type = tostring(report[2])
| where Type == 'sha256'
| extend SHA256 = tostring(report[3])
| where SHA256 !in(SHA256_whitelist)
| extend Tag = tostring(report[4])
| extend Tweet = tostring(report[5])
| project SHA256, Tag, Tweet
);
union (
TweetFeed
| join (
DeviceProcessEvents
| where Timestamp> MaxAge
) on SHA256
), (
TweetFeed
| join (
DeviceFileEvents
| where Timestamp> MaxAge
) on SHA256
), (
TweetFeed
| join (
DeviceImageLoadEvents
| where Timestamp> MaxAge
) on SHA256
) | project Timestamp, DeviceName, FileName, FolderPath, SHA256, Tag, Tweet
The MD5 counterpart of the query above, for the samples reported without a SHA-256.
Window: weekLookback: 30d
let MaxAge = ago(30d);
let MD5_whitelist = pack_array(
'XXX' // Some MD5 hash to whitelist.
);
let TweetFeed = materialize (
(externaldata(report:string)
[@"https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/week.csv"]
with (format = "txt"))
| extend report = parse_csv(report)
| extend Type = tostring(report[2])
| where Type == 'md5'
| extend MD5 = tostring(report[3])
| where MD5 !in(MD5_whitelist)
| extend Tag = tostring(report[4])
| extend Tweet = tostring(report[5])
| project MD5, Tag, Tweet
);
union (
TweetFeed
| join (
DeviceProcessEvents
| where Timestamp> MaxAge
) on MD5
), (
TweetFeed
| join (
DeviceFileEvents
| where Timestamp> MaxAge
) on MD5
), (
TweetFeed
| join (
DeviceImageLoadEvents
| where Timestamp> MaxAge
) on MD5
) | project Timestamp, DeviceName, FileName, FolderPath, MD5, Tag, Tweet
Elastic Stack
Index the CSV every 15 minutes, then join it from Kibana with ES|QL or alert on it with Watcher.
ES|QL reference
Elastic
Logstash ingest pipeline
#
Polls today.csv every 15 minutes and upserts into tweetfeed-iocs-*, keyed on type plus value so re-reported IOCs update in place.
Window: todayRefresh: 15 min
input {
http_poller {
urls => {
tweetfeed => "https://raw.githubusercontent.com/0xDanielLopez/TweetFeed/master/today.csv"
}
schedule => { every => "15m" }
codec => plain
}
}
filter {
split { field => "message" }
csv {
columns => ["date","user","type","value","tags","tweet"]
separator => ","
}
date { match => ["date","ISO8601"] target => "@timestamp" }
mutate { remove_field => ["message","event"] }
}
output {
elasticsearch {
hosts => ["https://es:9200"]
index => "tweetfeed-iocs-%{+YYYY.MM.dd}"
document_id => "%{type}-%{value}"
action => "update"
doc_as_upsert => true
}
}
Elastic
ES|QL join against DNS logs
#
A LOOKUP JOIN from your DNS index into the ingested IOCs, runnable straight from Kibana Discover or Lens.
Lookback: 24h
FROM logs-network-dns-*
| WHERE @timestamp > NOW() - 24h
| LOOKUP JOIN tweetfeed-iocs-* ON dns.question.name == value
| WHERE type == "domain"
| KEEP @timestamp, host.name, source.ip, dns.question.name, tags, tweet
| SORT @timestamp DESC
| LIMIT 1000
Fires whenever an internal endpoint resolves a TweetFeed domain. Save it under Stack Management → Watcher.
Interval: 15 min
{
"trigger": { "schedule": { "interval": "15m" } },
"input": {
"search": {
"request": {
"indices": ["logs-network-dns-*"],
"body": {
"query": {
"bool": {
"filter": [
{"range": {"@timestamp": {"gte": "now-15m"}}},
{"terms": {"dns.question.name": {"index": "tweetfeed-iocs-*", "id": "domain", "path": "value"}}}
]
}
},
"size": 100
}
}
}
},
"condition": {"compare": {"ctx.payload.hits.total.value": {"gt": 0}}},
"actions": {
"log": {"logging": {"text": "TweetFeed DNS hit: {{ctx.payload.hits.total.value}} events"}}
}
}
For detection rules, tweetfeed-iocs-* also works as a value list compatible with Elastic Security's value list exceptions - same data, alert-routable.
MISP & TAXII
Two standards-based ingest paths for Threat Intelligence platforms.
A MISP-format manifest with one Event per day and 365 days of history. In MISP go to Sync Actions → Feeds → Add Feed and give it the feed directory - MISP appends /manifest.json itself.
Window: 365 days
URL: https://tweetfeed.live/misp
Source format: misp
Provider: TweetFeed
Auto-pull: yes
Default tags: TweetFeed, tlp:clear, type:OSINT
Save, click Fetch and store all feed data to seed it, then enable Cron Jobs → Feed Pull for auto-refresh.
TAXII
TAXII 2.1 collection
#
STIX 2.1 indicators over a TAXII 2.1 endpoint, for OpenCTI, ThreatQ and anything else that speaks the standard.
Window: 365 daysMedia type: taxii+json 2.1
# 1. Discovery - lists the API root and its collections
curl -s https://api.tweetfeed.live/taxii2/ \
-H 'Accept: application/taxii+json;version=2.1'
# 2. Poll everything added since a timestamp (delta sync)
COLL=b7dc78af-1d12-5059-898c-3f0e77636204
curl -s "https://api.tweetfeed.live/taxii2/root/collections/$COLL/objects/?added_after=2026-07-01T00:00:00Z" \
-H 'Accept: application/taxii+json;version=2.1'
Paginate with limit and next. An added_after poll returns indicators only: the identity and marking-definition objects that every indicator references were created with the collection, so they fall outside any recent window. Fetch those once by id from /objects/{id}/ so your platform can resolve the attribution and the TLP:CLEAR marking.
TAXII reference
AI agents
The same corpus over the Model Context Protocol, so an agent can query it as a tool.
The corpus as agent tools over the Model Context Protocol, so a coding or SOC agent can query it directly instead of scraping a CSV.
Transport: streamable HTTPTools: 10
claude mcp add --transport http tweetfeed https://mcp.tweetfeed.live/
Ten tools: query_iocs, check_url, check_ip, check_hash, list_recent_iocs, get_tag_info, get_trending, enrich_ioc, get_campaigns and get_trends. No key, no auth.
Agents page and config snippets