Skip to content

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

  1. OS: macOS 26 (reproduction), observed on staging (main)
  2. Version: pyoaev 3.260821.0 (Nmap injector) and 3.260923.0 (SentinelOne collector, latest). pyoaev/signatures/ has no changes between these two versions.
  3. Other environment details: reproduced locally with Mimikyu (fake OpenAEV) + ofapi (fake SentinelOne API)

Reproducible steps

  1. Run the Nmap injector against an OpenAEV platform (or Mimikyu) and launch a scan inject on an asset with Detection/Prevention expectations.
  2. Look at the signature callback (execution_output_structured). Signature types are start_time, end_time, source_ipv4, source_ipv6, target_ipv4, ports_discovered, services_discovered.
  3. 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 by No 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 types

Expected 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): map ExecutionSignature fields to canonical SignatureTypes before building SignatureValues, and reject/skip keys that aren't valid SignatureTypes (e.g. partial_results, Nmap's ports_discovered / services_discovered). Add a test that checks the real payload types. The Nmap injector tests mock SignatureManager entirely, 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 the None signatures 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

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugType: something isn't working (fix:).needs triageNeeds triage from the Filigran product team.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions