From 47760269f8cffbe4b4fd65f8548ec711d1dac7c9 Mon Sep 17 00:00:00 2001 From: Willi Ballenthin Date: Wed, 22 Apr 2026 17:29:38 +0300 Subject: [PATCH] fix: correct capa/subscope-rule key in RuleMetadata.from_capa `RuleMetadata.from_capa` used `rule.meta.get("capa/subscope", False)` and `Field(False, alias="capa/subscope")`, but the actual key set by `_extract_subscope_rules_rec` is `"capa/subscope-rule"`. This caused `is_subscope_rule` to always be `False` in every `RuleMetadata` instance, making downstream filters in `render/utils.py`, `render/vverbose.py`, and `scripts/import-to-ida.py` ineffective (though subscope rules are already excluded from `ResultDocument` before reaching those callers). Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + capa/render/result_document.py | 4 ++-- tests/test_result_document.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b977dfe..ef74e6bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - ### Bug Fixes +- fix: correct capa/subscope-rule key in RuleMetadata so is_subscope_rule is no longer always False @williballenthin - fix: remove unreachable backports.functools_lru_cache fallback and dead dependency @williballenthin - fix: add missing ELF branch in get_format_from_extension for .elf_ files @williballenthin #3031 - fix: Scopes.from_dict uses cls instead of self so subclasses return the correct type @williballenthin diff --git a/capa/render/result_document.py b/capa/render/result_document.py index c8d194d2..87b67be2 100644 --- a/capa/render/result_document.py +++ b/capa/render/result_document.py @@ -658,7 +658,7 @@ class RuleMetadata(FrozenModel): description: str lib: bool = Field(False, alias="lib") - is_subscope_rule: bool = Field(False, alias="capa/subscope") + is_subscope_rule: bool = Field(False, alias="capa/subscope-rule") maec: MaecMetadata @classmethod @@ -674,7 +674,7 @@ class RuleMetadata(FrozenModel): examples=rule.meta.get("examples", []), description=rule.meta.get("description", ""), lib=rule.meta.get("lib", False), - is_subscope_rule=rule.meta.get("capa/subscope", False), + is_subscope_rule=rule.meta.get("capa/subscope-rule", False), maec=MaecMetadata( analysis_conclusion=rule.meta.get("maec/analysis-conclusion"), analysis_conclusion_ov=rule.meta.get("maec/analysis-conclusion-ov"), diff --git a/tests/test_result_document.py b/tests/test_result_document.py index 7775c033..c27f4237 100644 --- a/tests/test_result_document.py +++ b/tests/test_result_document.py @@ -13,11 +13,13 @@ # limitations under the License. import copy +import textwrap import pytest import fixtures import capa +import capa.rules import capa.engine as ceng import capa.render.result_document as rdoc import capa.features.freeze.features as frzf @@ -291,3 +293,30 @@ def test_rdoc_to_capa(): meta, capabilites = rd.to_capa() assert isinstance(meta, rdoc.Metadata) assert isinstance(capabilites, Capabilities) + + +def test_rule_metadata_is_subscope_rule_alias(): + rule = capa.rules.Rule.from_yaml( + textwrap.dedent(""" + rule: + meta: + name: test rule + scopes: + static: function + dynamic: process + authors: + - test + features: + - api: CreateFile + """) + ) + meta = rdoc.RuleMetadata.from_capa(rule) + assert meta.is_subscope_rule is False + + raw = meta.model_dump(by_alias=True) + assert "capa/subscope-rule" in raw + assert raw["capa/subscope-rule"] is False + + raw["capa/subscope-rule"] = True + meta_true = rdoc.RuleMetadata.model_validate(raw) + assert meta_true.is_subscope_rule is True