IOC Federation¶
An IOC (Indicator of Compromise) list is a versioned inventory of known-bad
artifacts: confirmed-malicious domains, file hashes, and package names. It sits
outside the heuristic score. When a PKGBUILD matches an IOC entry,
TrustSight emits a definitive, attributed finding ("this source domain is on
emiliano-go's known-bad list, incident atomic-arch-2026-06") with no score,
no band, and no probability language. This is detection, not inference.
The design rests on four principles:
- State, not rules. IOCs are observations about the world, so they live in the baseline layer next to the corpus baseline (A13/A13b), never in the rule config. An override or a weight cannot reach them.
- Attribution, not aggregation. Every match names the curator that flagged the artifact. The user sees who called it bad, and can follow the incident and evidence.
- Federation, not centralization. Independent curators publish signed baselines; TrustSight verifies, imports, and reports with source labels.
- Expiration, not permanence. IOCs carry a time-to-live. An expired indicator is reported as expired, never silently ignored and never permanently flagged.
The baseline format¶
A baseline is a directory:
manifest.json¶
{
"version": 1,
"source": "emiliano-go",
"created_at": "2026-08-09T00:00:00Z",
"expires_at": "",
"signature": "<hex ed25519 signature, or empty>",
"public_key": "<hex ed25519 public key, or empty>"
}
iocs.jsonl¶
One JSON object per line:
{"type": "domain", "value": "evil-cdn.xyz", "source": "emiliano-go", "confidence": "high", "provenance": "ASA-2026-06", "campaign": "atomic-arch-2026-06", "expires_at": "2026-09-15T00:00:00Z"}
{"type": "hash", "value": "deadbeef...", "source": "emiliano-go", "confidence": "high", "provenance": "vendor report"}
{"type": "package", "value": "malicious-aur-pkg", "source": "emiliano-go", "provenance": "ASA-2026-06"}
Any entry with an unknown type, an unusable value, or no source is
dropped with a warning; the rest import.
Indicator types and normalization¶
| Type | Match target | Normalization |
|---|---|---|
domain |
Host of a source= URL (or any referenced host) |
Lowercased, IDNA/punycode-folded, reduced to the registered domain, so a subdomain and the xn-- and unicode spellings all collapse to one value. |
hash |
Digests inside sha256sums / sha512sums / md5sums (and any other hex digest in the visible text) |
Lowercased; must be a hex digest of a known length (32/40/56/64/96/128). |
package |
pkgname / pkgbase and declared dependency names |
Lowercased, exact match. |
Signature¶
signature is a detached Ed25519 signature over the canonical
manifest.json (with the signature field emptied) concatenated with
iocs.jsonl byte-for-byte. Verification uses the public_key in the
manifest. An unsigned baseline imports only with --allow-unsigned, which is
for a baseline you built and trust locally.
How matching works¶
A dedicated IOC Match stage runs after rule matching and before scoring:
It reads the diff's added lines (or the whole PKGBUILD text when available)
and queries the stored indicators for every source-URL host, every checksum
and hex digest, and the package/pkgbase/dependency names. Hits are attached to
PackageFact.ioc_matches; see the report schema.
Each IocMatch carries type, value, source, confidence, provenance,
campaign, the surface it was found on (source_host, checksum_array,
package_name, a dependency field, ...), the line, and expired.
What an IOC match does not do¶
- It never changes the score. Matches live on
ioc_matches, never inscore_breakdown; the same PKGBUILD scores identically with or without a hit. IOCs are attribution the reviewer can verify, not a number. - It is not downgraded by a coverage gap, positive evidence, or an override. If an indicator matched in the analysed text, it is reported.
- It does not disappear when it expires. An expired indicator is still
reported, labelled expired, so a lapsed indicator never reads as clean. Only
trustsight ioc listhides expired entries by default (pass--include-expired).
These properties are enforced by the gates an IOC match carries its source,
IOC matches never contribute to the score, an expired IOC is never silent,
and IOCs are not in the rule config layer (see the
enforcement map).
Configuration and commands¶
The [baselines.ioc] section and its feeds are documented in
configuration. The
trustsight ioc {sources,import,update,list,export} commands are documented
in the CLI reference.
Storage is the ioc_entries table, keyed by (type, value, source).
Importing a baseline for a source replaces that source's rows and leaves other
sources (and expired rows) untouched, so imports are idempotent and
attributable.
Harvesting¶
Turning advisories and community reports into signed baselines is the job of a separate project, not TrustSight's codebase: a harvester proposes candidate entries, human curators review and sign, and TrustSight only ever consumes the signed output. Keeping the harvester out of the tool is what keeps the IOC gate non-evadable by construction: a hash either matches or it does not, and no code in TrustSight decides what belongs on the list.