[Bug] ECS Checks Overly Broad in Certain Integrations - #6491
Conversation
Bug - GuidelinesThese guidelines serve as a reminder set of considerations when addressing a bug in the code. Documentation and Context
Code Standards and Practices
Testing
Additional Checks
|
There was a problem hiding this comment.
Pull request overview
This PR tightens rule field validation so KQL/EQL/ES|QL queries only allow ECS fields that the referenced integration(s) actually declare (via ecs.yml-style files), with an explicit override mechanism for ECS fields injected by pipelines/agent defaults.
Changes:
- Add ECS scoping metadata to integration schema cache generation and use it to restrict ECS field validation to declared ECS fields.
- Introduce
integration-ecs-additions.jsonto allow-list ECS fields populated outside package field definitions, and wire it into both local and ES|QL remote validation paths. - Add a new devtools command (
find-ecs-scope-violations) plus unit tests to detect and validate ECS scope violations.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_specific_rules.py |
Updates schema union logic to use collect_schema_fields and exclude new metadata keys. |
tests/test_integrations.py |
Adds tests covering strict vs full ECS behavior, cache metadata expectations, and ES |
tests/test_all_rules.py |
Updates schema union logic to use collect_schema_fields and exclude new metadata keys. |
pyproject.toml |
Bumps project version to 2.0.5. |
detection_rules/rule.py |
Adjusts required-fields ECS classification based on integration-scoped schemas. |
detection_rules/integrations.py |
Adds schema-cache metadata (_ecs_declared, _uses_ecs_mappings), implements strict ECS scoping, and loads ECS additions. |
detection_rules/index_mappings.py |
Adds ES |
detection_rules/etc/integration-ecs-additions.json |
New override file listing pipeline/agent-injected ECS fields per package/data stream. |
detection_rules/ecs.py |
Excludes new schema-cache metadata keys when building flattened schema. |
detection_rules/devtools.py |
Adds find-ecs-scope-violations command for DaC users to identify impacted rules. |
|
Peer review with @Mikaayenson, instead of pulling the ingest pipeline fields into a non-ecs equivalent which is not versioned, we should instead explore baking this into the integration schema itself. |
| sequence by source.port, source.ip, destination.ip with maxspan=5s | ||
| [network where data_stream.dataset == "suricata.eve" and event.kind == "alert" and | ||
| event.severity != 3 and source.ip != null and destination.ip != null and | ||
| not source.domain : ("*nessusscan*", "SCCMPS*") and |
There was a problem hiding this comment.
That's fine. would be better to get this in separately to decouple the rule fix from this logic fix.
There was a problem hiding this comment.
Sure, will decouple, then we can merge from main into this and it will be gone 👍
sodhikirti07
left a comment
There was a problem hiding this comment.
The schema checks looks good! @eric-forte-elastic thanks for fixing this.
|
⛔️ Test failed Results
|
|
⛔️ Test failed Results
|
|
⛔️ Test failed Results
|
Mikaayenson
left a comment
There was a problem hiding this comment.
overall, lgtm. I tested locally against the release branches with no errors. We could probably refactor the helpers resolve_rule_packages, rule_datasets_by_package, rule_integrations_are_ecs_scoped, esql_indices_covered_by_packages to be a bit thinner, but not worth blocking over.
| # cover (o365 audit 3, crowdstrike alert 4); unmigrated streams declare far more (auditd_manager 42, | ||
| # network_traffic 77+, fortinet_fortigate 146). Non-ECS names in the file (entityanalytics_entra_id | ||
| # lists 36 `asset.*` names in ecs.yml) and multi-field expansions (`process.name.text`) do not count. | ||
| MIN_DECLARED_ECS_FIELDS = 20 |
There was a problem hiding this comment.
Where does this come from? Can we dynamically pull this from somewhere?
There was a problem hiding this comment.
This is an arbitrary limit, in effect I just picked 20, not the best solution, but potentially better than the alternatives. In prior commits, we folded in the sample event (so be pseudo dynamic) but that was not deterministic enough to work.
In short, to do this correctly we would need to know what each ecs@mappings populates per integration. Given that we do not, we have to have some way of picking which sets of integrations get assumed to have full ECS compatibility and those that do not.
In this case, If the fields are above 20, then the package gets strict validation. We are assuming that having less than 20 fields represents residual definitions left over from after converting to using the ecs@mappings component template (elastic/integrations#10135) If we were to use strict validation on everything, then we would have to add every not explicitly defined ECS field to non-ecs, notionally maintaining an ecs@mappings definition by hand.
As far as why I picked 20, I was going off of this distribution:
┌─────────────────────┬─────────┐
│ Declared ECS fields │ Streams │
├─────────────────────┼─────────┤
│ 0 │ 1 │
├─────────────────────┼─────────┤
│ 1 to 9 │ 120 │
├─────────────────────┼─────────┤
│ 10 to 19 │ 7 │
├─────────────────────┼─────────┤
│ 20 to 39 │ 13 │
├─────────────────────┼─────────┤
│ 40+ │ 61 │
└─────────────────────┴─────────┘
The 20 then determines which packages are in scope vs out of scope, but the packages can be manually moved if misclassified by the threshold.
There was a problem hiding this comment.
Peer reviewed with @Mikaayenson and we will be now trying a non-ecs based approach where everything is strict as initial tests show that the increase of non ecs fields is small.
|
|
||
|
|
||
| @cached | ||
| def elastic_agent_envelope_fields() -> dict[str, str]: |
There was a problem hiding this comment.
Does this change/need to be calculated or would a simple static dict suffice?
There was a problem hiding this comment.
A fully static dict will not work, as this needs to be checked for updated every time we refresh the beats or ECS schema (this would have changed with 9.5.0 as an example). We could generate a static dict or file when we regenerate the beats and ecs schemas but we would still need a function like this to inject them, so it would end up being more complex rather than less complex.
| f"{PACKAGE}.{INTEGRATION}.request.type": "long", | ||
| } | ||
| if scoped: | ||
| data_stream["_ecs_scoped"] = True |
There was a problem hiding this comment.
nit: instead of assigning _ecs_scoped here, what do you think about putting it higher in the structure? e.g. next to the integration version
"_meta": {
"ecs_scoped": ["dns", "http"] # or {"dns": true, "http": true}
}this way we dont have to strip _ prefixed keys etc.
you could then later do
version_schema.setdefault("_meta", {}).setdefault("ecs_scoped", [])
version_schema["_meta"]["ecs_scoped"].append(integration_name)| selected = { | ||
| name: info | ||
| for name, info in flat.items() | ||
| if name.split(".", 1)[0] in ELASTIC_AGENT_ENVELOPE_FIELDSETS |
There was a problem hiding this comment.
Question from peer review with @Mikaayenson, can they be static? They may either always be the same, or change infrequently enough that we can include them in the shema objects (json)
There was a problem hiding this comment.
We may be able to do this dynamically on build instead of on ingest.
There was a problem hiding this comment.
Generally speaking not worth it given: #6491 (comment)
Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>
9fe1eb9 to
80624e0
Compare
Mikaayenson
left a comment
There was a problem hiding this comment.
im in favor of transparency despite the growing non-ecs-schema file. we can either work with the integration teams to update the integration if needed, but explicitly defining areas where the integration does / does not emit ecs fields will help us catch runtime errors.
| from .utils import cached, get_etc_path, read_gzip, unzip | ||
|
|
||
| if TYPE_CHECKING: | ||
| import zipfile |
There was a problem hiding this comment.
do we need this if we're using quoted "zipfile.ZipFile"
|
Tested on release branches, found gap addressed in ae80907 Testing artifacts |



Pull Request
Issue link(s):
Resolves https://github.com/elastic/ia-trade-team/issues/1036
Related: https://github.com/elastic/ia-trade-team/issues/1028
Summary - What I changed
KQL/EQL rule validation passed for ECS fields that a rule's related integrations never populate. The canonical example: a
network_trafficrule usingprocess.titlevalidated successfully, although thenetwork_trafficpackage does not populate that field for any of its data streams. The cause was that integration validation unioned the full ECS schema into every package's field schema, so any ECS field was accepted for any integration.This PR removes that union. Integration validation now checks a query against:
fields/*.yml, withexternal: ecsmulti-fields such asprocess.name.textexpanded), plusagent.*,cloud.*andhost.*sets,data_stream.*,event.ingested,event.agent_id_status), folded into every data stream whenintegration-schemas.json.gzis built, plusdetection_rules/etc/non-ecs-schema.jsonentries for the rule's index patterns, as before.Every integration is treated the same way. There is no per-package classification of which integrations get strict validation: a package that relies on the
ecs@mappingscomponent template and does not declare the ECS fields it populates has those fields added tonon-ecs-schema.jsonunder the rule's index pattern, in the same place we already record vendor-specific fields.Legacy approaches/Context
KQL/EQL rule validation passed for ECS fields that a rule's related integrations never populate. The canonical example: a
network_trafficrule usingprocess.titlevalidated successfully, although thenetwork_trafficpackage does not populate that field for any of its data streams. For this integration only a subset of ECS fields are supported. In thenetwork_trafficcase, it is inecs.yml. Not every integration specifies a subset, but where they are specified they are authoritative and the field validation has been upgraded to match this.Primary change in the code is to restrict the ECS fields to the subset if the integration specifies a subset. Additionaly, for cases where we can manually confirm that there is an ecs field present, this PR adds another varient of a non-ecs schema called detection_rules/etc/integration-ecs-additions.json.
Legacy approach
No longer needed.
For DaC users, we also added a
find-ecs-scope-violationsas this change may require a number of custom rules to be updated. This command can be used to help triage those rules. This can be especially useful if one is using auto generated schema where this change would not be overt as it would end up being automatically added to the auto gen schema.This command can be run via
python -m detection_rules dev integrations find-ecs-scope-violationsHow To Test
Before (main):
After (this branch):
Fields the integration does declare (e.g.
destination.ipfornetwork_traffic/icmp)continue to validate, as do all ECS fields for
ecs@mappingspackages (cloud_defend).Schema regeneration (self-backfills any cache entry missing the new metadata):
$ python -m detection_rules dev integrations build-schemasChecklist
bug,enhancement,schema,maintenance,Rule: New,Rule: Deprecation,Rule: Tuning,Hunt: New, orHunt: Tuningso guidelines can be generatedmeta:rapid-mergelabel if planning to merge within 24 hoursContributor checklist