feat(intel,ingester): mal_hash feed + observed_attachments table (DEBT-046)
New MalHashProvider sibling ABC (decnet/intel/base.py) since SHA-256 is a different keyspace from IntelProvider's IPs. MalwareBazaarProvider mirrors FeodoProvider's bulk-feed shape: 24h refresh via _ensure_fresh / _refresh, in-memory set[str] of hex-lowercased hashes, set-membership lookup. Auth-keyed via DECNET_MALWAREBAZAAR_AUTH_KEY; absent key silent-no-ops the lane (single warning, no HTTP traffic). Per-hash observations persist to a new observed_attachments table. DECNET is a honeypot platform — every attachment hash an attacker delivers is intel, regardless of whether anyone classified it. Verdict is sticky: True never downgrades to False/None on subsequent observations. Out of scope: API surface, federation export, retention. Ingester _publish_email_received calls the provider for each attachment sha256, sets mal_hash_match on the bus payload (omitted entirely when the message had no attachments — keeps R0046's `is True` predicate silent on hash-less mail, matching pre-paydown behavior), and upserts the row regardless of provider availability.
This commit is contained in:
@@ -313,6 +313,27 @@ class BaseRepository(ABC):
|
||||
"""Retrieve the keystroke-dynamics profile row for a session."""
|
||||
pass
|
||||
|
||||
async def upsert_observed_attachment(
|
||||
self,
|
||||
*,
|
||||
sha256: str,
|
||||
decky_uuid: Optional[str],
|
||||
attacker_uuid: Optional[str],
|
||||
extension: Optional[str],
|
||||
subject: Optional[str],
|
||||
mal_hash_match: Optional[bool],
|
||||
mal_hash_match_provider: Optional[str],
|
||||
) -> str:
|
||||
"""Record one observation of *sha256* against ``observed_attachments``.
|
||||
|
||||
Returns the row UUID. Verdict semantics: ``True`` is sticky;
|
||||
once set, subsequent ``False`` / ``None`` observations don't
|
||||
downgrade. See :class:`ObservedAttachment` for the full column
|
||||
list and the rationale (DECNET as a honeypot platform — every
|
||||
delivered hash is intel, even before any provider classifies).
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def upsert_attacker_intel(self, data: dict[str, Any]) -> str:
|
||||
"""Insert or update the threat-intel row for an attacker UUID.
|
||||
|
||||
Reference in New Issue
Block a user