- Rust 97%
- JavaScript 1.4%
- CSS 0.8%
- Lua 0.4%
- Python 0.3%
- Other 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
ci/woodpecker/push/test Pipeline was successful
Add Docker examples |
||
| .github/workflows | ||
| .woodpecker | ||
| reference-daemon | ||
| LICENSE | ||
| proposal.md | ||
| README.md | ||
ActivityPub Threat Intelligence (AP-TI)
AP-TI lets organisations share network threat intelligence over ActivityPub. The shared observables are IP addresses, prefixes and domains linked to abuse such as SSH brute force, spam, scanning or command-and-control.
STIX/TAXII and MISP work well inside a fixed sharing community, but they have no open discovery. ActivityPub adds discovery (WebFinger), a follow graph and domain-bound identity, so participants can find each other across organisational boundaries.
Core ideas
- Evidence types. Publishers share Sightings (first-hand observations), ThreatIndicators (curated claims with a validity period) and Opinions (agreement, disagreement or allowlisting). The terms follow STIX 2.1, so aggregators can map them directly to STIX/TAXII.
- Nothing is listed forever. All evidence ages out. Peers cannot set expiry for others: each consumer computes an effective expiry from fresh evidence under its own trust policy.
- Local trust policy. Following an actor does not mean trusting it. Consumers set trust and weights per operator and behaviour. Sightings activate an observable only when a weighted quorum of trusted operators reports it. Trusted disagreement or allowlist entries can suspend it.
- TLP-aware sharing. Every object has a Traffic Light Protocol level (CLEAR, GREEN, AMBER, AMBER+STRICT). The level decides how the object is addressed and who may fetch it.
- Hardened against abuse. The spec covers poisoning, Sybil attacks, replay, origin checks and special-purpose addresses, and adds privacy guidance for personal data such as IP addresses.
Enforcement points (firewalls, fail2ban, DNS RPZ and so on) never use ActivityPub themselves. They read an aggregator's exported active list.
Sharing observables between organisations
Every receiving organisation decides on its own how much weight the evidence
of each peer carries. The rules are in Sections 5.2, 7 and 8 of the
spec. The config.example.toml
names used by the reference daemon are given in brackets.
Trust and weights
Trust and weight are set per operator (the organisation behind one or more
actors), not per actor. They can also be set per behaviour, for example to
trust a partner for ssh-bruteforce but not for smtp-spam.
- Trust (
trusted, defaultfalse). Evidence from untrusted operators never affects the active list. The reference daemon puts it in a review queue instead. Following a peer only means receiving its evidence. It does not mean trusting it. - Weight (
weight, default 1, must be ≥ 0). This is how much a trusted operator counts towards the quorum and in disputes. An operator counts once, however many Sightings or actors it has. Weight 0 means the operator is trusted but counts for nothing in the quorum or in disputes. Its Indicators and allowlist entries still apply. - Own organisation. The daemon always trusts its own sensors, with weight
local_weight(default 2).
Quorum
Sightings (raw observations) list an observable only when enough trusted
operators report it. ThreatIndicators from a trusted operator do not need a
quorum. They stay listed until their own validUntil, but never longer than
the maximum evidence age M.
- Threshold
k(default_k, orkper behaviour, default 2). The summed weight of operators with recent Sightings must reach at leastk. Withk = "off", Sightings never list an observable alone and only Indicators do. The example config uses this forcommand-and-control. - Expiry. Sort the operators by their latest
lastSeen, newest first, and add up their weights. ThelastSeenof the operator at which the sum reacheskis the quorum time L. The entry expires at L + min(T, M), where T is the Sighting TTL and M the maximum evidence age of the behaviour. Nobody else can extend an expiry. Once trusted operators stop reporting an observable, it drops off the list. - Disputes. Trusted
disagreeorstrongly-disagreeOpinions add up to a dispute weight D. Supporting operators (valid indicator, Sighting within T,agreeOpinion) add up to a support weight S. If D > 0 the entry is flagged for review. If D ≥ S it is suspended. A trusted allowlist entry always suspends it.
Example with k = 2, local_weight = 2 and two partners at weight 1:
Who reports 45.13.7.9 for ssh-bruteforce |
Summed weight | Listed? |
|---|---|---|
| Partner A only | 1 | No |
| Partners A and B | 2 | Yes, until the older of their two lastSeen + T |
| Own sensors only | 2 | Yes. Set local_weight below k to require peer confirmation. |
| Partner A with weight 2 | 2 | Yes. A weight equal to k lets one operator list observables alone. |
High weights and k = 1 make a single compromised operator enough to list
observables. Keep weights below k unless you trust that operator to act
alone.
TLP
Every evidence object carries a TLP 2.0 level.
The publisher sets it (default_tlp, or tlp per behaviour; default
green). A local sensor can also request a TLP per observation, e.g.
apti-rspamd for sending IPs and spam domains separately. The level decides
how the object is delivered and who can fetch it:
| TLP | Delivered to / readable by |
|---|---|
clear |
Anyone. May be addressed to the public collection. |
green |
Followers only. The daemon approves follow requests manually by default. |
amber, amber+strict |
Only the named recipient actors configured when the object was first published. Its later Updates and Delete go to the same recipients. |
red |
Never shared over AP-TI. |
- Objects above
clearneed a signed fetch. The publisher returns only objects the requesting actor is allowed to see. - Receivers must not
Announceor re-share evidence beyond its TLP. The reference daemon publishes only its own observations and never peer evidence. - A Sighting that confirms an indicator (
indicatorRefs) must not have a less restrictive TLP than that indicator. The daemon drops Sightings that break this rule. - On the active list, each entry carries the most restrictive TLP of the
evidence that supports it (indicators, Sightings and
agreeOpinions). API tokens have amax_tlp, and entries above it are left out. For example, an enforcement tool whose token allows onlygreennever gets an entry that rests onamberevidence. - The spec recommends
greenor stricter for scan, brute-force, spam and DDoS sources, so that attackers are not warned that they were detected.
Repository layout
| Path | Contents |
|---|---|
proposal.md |
The protocol specification (Internet-Draft style, draft-activitypub-threatintel-00): data model, publication, lifecycle, effective-expiry algorithm, trust, security and privacy. |
reference-daemon/ |
Rust reference implementation following the deployment model in Appendix D of the spec. |
.github/workflows/, .woodpecker/ |
CI: formatting and tests on every change; release binaries for tags of the form vYYYY.MM.N. |
Reference implementation
The reference-daemon Cargo workspace contains:
| Component | Role |
|---|---|
apti-core |
Library: data model, normalisation, trust policy and effective-expiry engine. No I/O. |
aptid |
Daemon: ActivityPub federation, SQLite storage, internal REST API for sensors and enforcement, management control socket. |
apti-tui |
Terminal UI for following peers, setting trust, working through the review queue and managing allowlists. |
apti-fail2ban |
Connector: reports fail2ban bans to aptid and writes the active list back to files that fail2ban bans from. |
apti-allowlist |
Connector: keeps the local allowlist in sync with a hand-edited text file (append or source-of-truth mode). |
apti-rspamd |
Connector: reports IPs and SPF-authenticated envelope-from domains that keep sending spam according to Rspamd to aptid (with a separate TLP for each), and serves the active list to Rspamd as multimap files (IPs, URL and sender domains). |
apti-dashboard |
Tool: renders a static HTML world map of where findings come from (GeoIP), with a time slider and filters for behaviour and own vs. federated evidence. |
A typical deployment works like this:
- Local sensors (for example fail2ban) push observations to
aptid. aptidbatches them into Sightings and federates them to followers.aptidcombines its own evidence with trusted peer evidence into an active list.- Enforcement tools pull that list.
To get started:
cd reference-daemon
cargo build --release
cargo test --workspace
See reference-daemon/README.md for
configuration, the REST API, the fail2ban connector, ActivityPub endpoints,
the TUI, and known limitations.
Status
Experimental, work in progress. The specification is a draft and may change. The JSON-LD context URI is still a placeholder.
License
Apache License 2.0, see LICENSE.