Repository navigation
fix(signatures): SignatureManager sends non-canonical signature types (start_time, source_ipv4, ...) that break collectors #352
Description
Description
SignatureManager.build_payload sends the field names of ExecutionSignature as signature types: start_time, end_time, source_ipv4, source_ipv6, target_ipv4, target_ipv6, target_hostname. The canonical names are start_date, end_date, source_ipv4_address, source_ipv6_address, target_ipv4_address, target_ipv6_address and target_hostname_address.
# pyoaev/signatures/signature_manager.py - build_payload
signature_data = signature.model_dump(exclude_none=True)
...
SignatureValue(signature_type=key, signature_value=value)
for key, value in signature_data.items()The platform stores these strings as is. Collectors then validate them against SignatureTypes, so building DetectionExpectation / PreventionExpectation fails:
5 validation errors for PreventionExpectation
inject_expectation_signatures.0.type
Input should be 'process_name', ... [type=enum, input_value='start_time', input_type=str]
expectations_models_for_source builds the whole batch at once, so one bad expectation fails the fetch. The collector then receives no expectations on every cycle, and valid expectations from other injects (e.g. EICAR on an agent) never get matched and expire as FAILED.
Same symptom as OpenAEV-Platform/openaev#7114. That issue says "no code path emits the legacy names anymore", but SignatureManager (#206, June 2026) does. The migration in OpenAEV-Platform/openaev#7115 only repaired existing rows, so each new run of an injector that uses SignatureManager brings the bug back (Nmap confirmed; netexec, nuclei and email also use SignatureManager).
Extra signatures go through unchanged too. Nmap sends ports_discovered / services_discovered, which aren't in SignatureTypes and have list values.
Environment
- OS: macOS 26 (reproduction), observed on staging (main)
- Version: pyoaev 3.260821.0 (Nmap injector) and 3.260923.0 (SentinelOne collector, latest).
pyoaev/signatures/has no changes between these two versions. - Other environment details: reproduced locally with Mimikyu (fake OpenAEV) + ofapi (fake SentinelOne API)
Reproducible steps
- Run the Nmap injector against an OpenAEV platform (or Mimikyu) and launch a scan inject on an asset with Detection/Prevention expectations.
- Look at the signature callback (
execution_output_structured). Signature types arestart_time,end_time,source_ipv4,source_ipv6,target_ipv4,ports_discovered,services_discovered. - Run any collector (e.g. SentinelOne) while those expectations are pending. Each cycle logs
Error fetching expectations: N validation errors for PreventionExpectation ... input_value='start_time'followed byNo expectations found to process.
Minimal check without a platform:
import os
from pyoaev.signatures.signature_manager import SignatureManager
from pyoaev.signatures.models import NetworkInjectorConfig
os.environ["CONTAINER_IP"] = "10.0.0.5"
sig = SignatureManager(client=None).build_execution_signatures(
NetworkInjectorConfig(target_ipv4="192.0.2.11")
)
print(list(sig.model_dump(exclude_none=True)))
# ['start_time', 'source_ipv4', 'source_ipv6', 'target_ipv4'] -> sent as signature typesExpected output
The signature payload only uses SignatureTypes values (start_date, end_date, source_ipv4_address, target_ipv4_address, ...). Collectors parse pending expectations and match them.
Actual output
The payload uses the ExecutionSignature field names as types. Every collector that fetches those expectations fails validation on each cycle, and all of its expectations end up FAILED.
Additional information
Suggested fix:
build_payload(pyoaev/signatures/signature_manager.py): mapExecutionSignaturefields to canonicalSignatureTypesbefore buildingSignatureValues, and reject/skip keys that aren't validSignatureTypes(e.g.partial_results, Nmap'sports_discovered/services_discovered). Add a test that checks the real payload types. The Nmap injector tests mockSignatureManagerentirely, so this wasn't caught.expectations_models_for_source(pyoaev/apis/inject_expectation/inject_expectation.py): build expectations one by one and log/skip the invalid ones, so one bad expectation no longer blocks the whole batch (same idea as theNonesignatures fix for collectors#490).- Follow-ups:
- release pyoaev and bump the pin in nmap (currently
==3.260821.0), netexec, nuclei and email - on the platform: normalize or reject unknown signature types at ingestion, and run a new repair migration for rows written since #7115
- release pyoaev and bump the pin in nmap (currently
Activity
guzmud commentedon Oct 6, 2026
Nota bene: some elements were added to
SignatureTypesas legacy support during the rewrite of microsoft defender. AFAIK, we haven't analyzed which injectors and which collectors are using those "legacy" (I guess) elements compared to which ones are using the more recent ones.