Attacker background noise
A community can designate a few workstations and a few servers as compromised. They carry on with their working day — the accountant keeps doing accounts — and an attacker's noise is added to it: a beacon towards a command server, a payload download, reconnaissance, credential theft, an exfiltration.
What makes that noise interesting is not that it exists, it is that it is consistent end to end. For one and the same gesture, at the same second, the firewall, the proxy, the DNS resolver and the EDR name the same indicator of compromise. That is what gives an analyst something to correlate — and it is what no noise generator gives when each technology draws its destination on its own.
The keystone: an indicator is a destination
SekoiaCraft already described a company's day and had a dozen technologies tell it identically: a gesture carries its facts — the destination, the process, the document — and all the projections of that gesture read them. The attacker noise merely comes in through that door.
Four threat gestures were added to the catalogue, and they name a destination:
| Gesture | What the logs show of it |
|---|---|
| Malicious domain resolution | The question asked of the resolver — BIND, Sysmon, the four EDRs |
| Beacon to a command server | The outbound connection — firewall, proxy, agent socket |
| Payload download | The same, plus the URL at the proxies |
| Data egress to the attacker | The same, on the upload side |
Each family is read through the field it actually logs: a firewall sees a socket — an address and a port — a proxy sees a transaction — hence a name — a resolver sees a question. They do not render the same field; they render the same row of the indicator catalogue, which is what makes it impossible for them to contradict each other.
The destination of a threat gesture is drawn on (entity, day) and not on the gesture: an implant has
one configuration for the day. Resolving the domain, beaconing to it, downloading the payload from
it and egressing data to it therefore name the same host — otherwise the chain would contradict itself
from one link to the next, and the contradiction would have been moved rather than removed.
The binary talking to the indicator comes from a clean pool — schtasks.exe, rundll32.exe,
mshta.exe, transcribed from the reference alert corpus. Without it, the EDR announced chrome.exe
beaconing to a command server: a message contradicting itself in two neighbouring fields.
Where the indicators come from
Two sources, and they do not tread on each other.
The floor, seeded with the product. About a dozen hosts and addresses in the ranges reserved for documentation (RFC 5737) and on reserved domains (RFC 2606). They belong to nobody and have no intelligence value — their only role is to guarantee the noise stays consistent on an installation without an API key. Without them, a campaign would have no host to name.
The Sekoia feed, once an API key is set — but setting the key fetches nothing by itself. The feed is queried by Refresh now, or every day when Refresh the catalogue daily is ticked (it is not, by default). It returns active indicators — domains, addresses, URLs — modified since midnight UTC: early in the day it may have nothing to return, and the catalogue is then left as it is. Sekoia's CTI rules fire on what the simulation emits only when a message carries one of those indicators.
A successful refresh touches the modification date of every community with a grafted threat — the channel by which the scheduler learns that its day plan is stale. The day is rebuilt at the next tick, and a compromised host's indicator changes there and then rather than at midnight. The counterpart: the communities list, sorted by that date, reorders.
The refresh replaces only what the feed had written: a seeded row, or one entered from the datasets, survives. And a feed that returns nothing leaves the catalogue as it is — replacing a dated catalogue with an empty one would be strictly worse.
The screen therefore announces two figures: what the catalogue holds, and how much of it comes from the feed. The first is never zero on a seeded database — that is the floor, and it is what you look at when the second is zero.
The key is entered under Configuration → General settings. Like the intake keys, it is never read
back: only its mask returns (••••1a2b), and the contract is the same — field left empty = kept,
“erase” box ticked = erased, value entered = replaced. It is not encrypted in the database, and the
screen says so: the decryption key would live on the same host as PostgreSQL.
The campaigns: one per hour
The threats live in the scenario library. Six of its scenarios are campaigns — and when grafted, only one plays at each hour of the day. That is what an attacker does: one thing at a time, and something else the next hour.
Six “1 day in 6” rarities would not have said that: they would give a day with no campaign at all one time in three, and two simultaneous campaigns almost as often.
| Campaign | What it chains |
|---|---|
| Balise vers le serveur de commande | Resolution → beacon every 10 minutes |
| Téléchargement de charge | Resolution → retrieval → payload execution |
| Reconnaissance | Directory enumeration |
| Vol d’identifiants et rebond | Secret extraction → pivot to another host |
| Exfiltration | Upload → detected bulk egress |
| Ancrage et persistance | Implant wake-up → anchoring on the machine |
“Battement de l’implant” is a background scenario: a heartbeat every two hours, outside the rotation. That is what makes a compromised host visible all day long, and not only during its campaign's hour.
Any scenario of the library targeting workstations or servers can join the rotation — a business email compromise, a password spray — and each is replayed in every hour that falls to it, from the start of that hour.
Grafting the threat onto a community
The Threats tab of a community, first card:
| Setting | What it decides |
|---|---|
| Scenarios in rotation | Which scenarios take turns on the compromised hosts |
| Quiet hours | Empty outlets of the rotation: hours without a campaign |
| How to designate them | By a number, or by name — three modes |
| Compromised workstations | How many users of the population |
| Compromised servers | How many machines of the fleet |
| Named workstations / servers | The named hosts, one per line |
With two scenarios and one quiet hour, one hour in three has no campaign. The rotation is drawn again every day, and each compromised host plays only the steps of its slice: a server does not receive the phishing email of a business email compromise, it receives its privilege escalation.
It is a graft, never an assignment. The threat is composed on top of the host's business day: the accountant keeps their accounting, and the attacker's noise is added to it. Assigning it would have made them a full-time attacker, silent about their job — and the malicious traffic would have had nothing left to be read against.
A compromised server playing no business archetype plays the threat alone: that is the normal case for a fleet, the vast majority of whose machines play nothing.
Three ways of designating the compromised
| Mode | What it says |
|---|---|
| Stable draw | A number, and always the same hosts. The default. |
| Named hosts | These ones, by name. The numbers no longer apply. |
| Rotating cohort | A number, and a cohort of the day within which the threat changes host every hour. |
Under a stable draw, who is compromised is not stored: the community says how many, and a stable
draw on (community, name) says who. Increasing the number swaps nobody: the compromised of a count
of 3 are the first three of those of a count of 5.
Naming the hosts is the only thing a draw cannot do — naming that machine. The list is then the truth, and both numbers no longer apply. A declared counterpart: a list goes stale. A name the population no longer carries is therefore named on screen rather than believed, while a name unknown at the moment of saving is refused on the spot — at write time it is a typo, later it is a rename. Changing population empties both lists, as it already empties the assignments.
The cohort rotates hourly, and the grain is no accident. It is already the unit at which an
attacker changes task in this product — a fork's hourly cadence says so too. Any finer, and the
chain would contradict itself from one link to the next: the command server's configuration is drawn
by (entity, day), so that the host resolving the malicious domain at 09:14 and the one beaconing at
09:15 would name two different indicators — exactly the contradiction the attacker background
noise exists to remove.
The cohort of the day is wider than the number of hosts noisy at any one time: it is the compromised perimeter, and the rotation says which of its hosts is noisy at that hour. An attacker who has gained a foothold pivots within a handful of machines; they do not visit the whole fleet every hour. It is also what keeps the list nameable — a dozen hosts, not two hundred.
The screen therefore names the cohort, never “those of the current minute”: such a list would be stale the second after, and above all would not be the one the day's plan targets.
In all three modes, the screen names the hosts — a figure does not say where to look.
Saving the threat touches the community's modification date, which is the channel by which the configuration tells the scheduler its day plan is stale. Grafting the threat at 2 pm therefore takes effect on the next tick.
Launching a scenario now
The same tab, second card: Launch a scenario. Any scenario of the library — including the denial of service, which no graft can play — runs now, on this community, for a bounded duration:
| Field | What it decides |
|---|---|
| Scenario | Which scenario of the library |
| Duration (minutes) | How long it lasts — sixty by default, a day at most |
| Hosts drawn per slice | How many workstations, servers or sites it targets when none is named |
| Name the hosts instead | Workstations, servers and sites by name, one per line |
Preview says what the launch would send, without sending anything; Launch now queues it. The scenario starts at the next minute, and each minute leaves at the end of that minute, with the real time in every message — a launch at 23:50 does not stop at midnight.
The preview also lists the messages, in emission order, each folded on a line — time, host, technology, event type, step — that unfolds onto the message exactly as it would leave. Those that would not leave are listed too, marked with their fate: No intake key, Invalid render, Render failed; it is by seeing a technology's messages that you know which key to set. The screen shows the first 200 and says so; Download the … messages (JSON) gets them all. The file replays the preview — same seed, same start, same hosts — rather than storing it: a setting changed in between, a key set for instance, shows in the file.
Hosts that are not named are drawn anew on every preview and every launch: two launches of the same scenario target, as a rule, different workstations, servers or sites — and so does a launch that follows a preview. To send what a preview showed, Launch on these hosts, under its result, runs the scenario on the hosts it drew, by name. A host named in one slice means “those and no others”: nothing is drawn in the other slices.
What does not leave is said, never hidden: the steps that no active technology covers, the steps no target can play, the technologies without an intake key. And a launch emits the alerts its steps call for when the technology has nothing else to describe them with — the phishing email, the mailbox rule, the risky sign-in are only described that way.
The third card lists the launches of the community: scenario, start, duration, targets, and progress — sent out of planned, read on the dispatched batches and never stored. The page refreshes as batches leave.
On a team of a pool, a launch leaves on every team; the pool page launches it too, and follows it for all of them — see Community pools.
Tracing a threat to the platform
Every threat message that leaves is written to the journal of api-activity, under its own
[Menace] context — one line per message queued, that is per gesture and per technology:
docker compose -f infrastructure/docker/docker-compose.dev.yml logs -f api-activity | grep '\[Menace\]'
On a deployed host, the same logs command targets that environment's compose file, as for
the initial password. The lines are in French, like the rest of the
journal:
« Acme » 2026-09-14T08:02:31Z poste jdoe (PC-JDOE-01) · Battement de l’implant → Fortinet FortiGate / traffic-forward · IOC evil-c2.net (domain-name, feed CTI) : ABSENT du message, seule l’adresse dérivée 198.51.100.14 y figure · activité <activityId> · lot c.<community>.2026-09-14.482.<technology>.0
- The instant — the time the message itself carries, in UTC: the one the platform indexes it under.
- The host — the workstation and its machine name, or the server and its address.
- The gesture — the scenario step that produced it.
- The technology and event type — hence the intake to search.
- The IOC — its value, its kind, and its origin: the feed, the seeded floor, or the datasets.
- The verdict — whether the message carries the indicator, or only its address.
- The activity — shared by every technology describing the same gesture.
- The batch — the identifier
api-senderwrites next to the HTTP status of the batch.
To find the message on the platform: in the intake of that technology, around that instant, search for the value the verdict says is present.
Each time a day is built — at midnight, on a restart, after a refresh of the feed — a summary line says how many threat gestures and messages the day plans, how many of the catalogue's indicators come from the feed, and which indicator each compromised host targets today. It is a warning when the day beacons without any CTI rule able to match it.
Why no alert?
- No indicator from the feed. The summary says
0 du feed: the key is set, but the feed has not been queried, or returned nothing (see above). The beacons target the seeded floor. - The day was planned before the refresh. A successful refresh now replans the day, and the summary line is written again.
- The indicator does not appear in the message. Firewalls, EDR sockets and endpoint agents (FortiGate, Palo Alto NGFW, Cortex, CrowdStrike, HarfangLab, the Sekoia agent, SentinelOne) only write an address — and for a domain or a URL from the feed, that address is derived in a documentation range that no CTI rule knows. The name only appears at the proxies (Netskope, Zscaler), in Windows (Sysmon network connection) and in Defender — and in the DNS logs, which only the campaigns that resolve a malicious domain produce — “Balise vers le serveur de commande”, “Téléchargement de charge”. A rotation reduced to the implant heartbeat resolves nothing.
- Detection gestures emit nothing in the rotation. Reconnaissance, credential theft, persistence… are reserved for alerts: only a scenario launched on demand emits them (see above).
- The technology does not emit: not retained by the community, cut after a series of failures, or without an intake key.
Editing a grafted scenario takes effect on the next tick, like any setting of the community: saving its steps touches the modification date of the communities grafting it. Only the minutes not yet sent change; the gestures already sent stay as they left.
What this does not do
- The detection gestures — reconnaissance, brute force, credential theft, malware, pivot, exfiltration, persistence — still name no destination, and are emitted only by a launched scenario.
- A single rotation per community. A second one would need a second set of quiet hours; two scenarios in the same rotation already say “one or the other”.
- A risky sign-in still carries the user's address, not an attacker's: the source address of an authentication gesture is that of the entity.
- Expired indicators are not swept: the refresh replaces the feed's rows wholesale, which is enough as long as it runs. Without it, the catalogue ages without saying so.
- The alert families of the legacy project — Elevate, Sigma, POI — are not ported: a launch plays the gestures of a scenario, not those rule sets.