YARA schema reference¶
Use this page to look up the ZettelForge YARA parser, CCCS metadata validation, entity mapping, ingest API, and command-line interface. The reference covers ZettelForge 2.8.0, released under the MIT license.
Modules: zettelforge.yara, zettelforge.yara.parser,
zettelforge.yara.cccs_metadata, zettelforge.yara.entities,
zettelforge.yara.tags, zettelforge.yara.ingest
In progress
Parsing, core metadata validation, entity mapping, persistence, and dry-run
ingestion work in ZettelForge 2.8.0. Modifier flags, several optional CCCS
validators, reference mapping, strict live-ingest exit status, dry-run
symlink protection, and CLI explainer integration remain incomplete. See
Current limitations.
Prerequisites¶
You need ZettelForge 2.8.0 and Python 3.10 or later. Use Configure YARA ingestion for a task-oriented import procedure. Read Detection rules schema for the shared Sigma and YARA model.
Public exports¶
zettelforge.yara exports these names:
| Name | Kind | Purpose |
|---|---|---|
YaraRule |
dataclass | YARA-specific DetectionRule subtype |
YaraParseError |
exception | Reports file stat and size errors |
YaraValidationError |
exception | Reserved validation exception; the 2.8.0 APIs return ValidationResult instead of raising it |
parse_yara |
function | Parses YARA source text |
parse_file |
function | Reads and parses one rule file |
validate_metadata |
function | Validates flattened metadata at a selected tier |
rule_to_entities |
function | Builds a YaraRule and relation dictionaries |
resolve_yara_tag |
function | Resolves supported inline tags to typed entities |
ingest_rule |
function | Persists the first rule from one input |
ingest_rules_dir |
function | Walks and ingests a rule directory |
zettelforge.yara.parser.parse_text is a legacy alias for parse_yara. It is
available from the parser submodule but is not a package-level export.
Processing layers¶
| Layer | Module | Responsibility |
|---|---|---|
| Parser | yara.parser |
Wrap Plyara and normalize parsed dictionaries |
| Metadata | yara.cccs_metadata / yara.schemas |
Load the CCCS metadata definitions and apply the implemented validators |
| Entity | yara.entities / yara.tags |
Map a parsed dictionary to YaraRule and relation dictionaries |
| Ingest | yara.ingest |
Store each rule as a memory note and write knowledge-graph edges |
Vendored CCCS schemas¶
ZettelForge packages two CCCS YARA metadata files in
src/zettelforge/yara/schemas/:
| File | Purpose |
|---|---|
CCCS_YARA.yml |
Declares metadata fields, formats, uniqueness, optionality, and validator names |
CCCS_YARA_values.yml |
Declares allowed status, sharing, category, malware-type, actor-type, and hash patterns |
The YAML files come from the Cyber Centre's CCCS-Yara project and retain its MIT
license. ZettelForge does not vendor the upstream validator_functions.py;
cccs_metadata.py implements the validation logic locally. The module loads
both YAML files at import time, so a missing or unreadable file prevents the
module from importing.
Metadata validation¶
Signature:
validate_metadata(rule_meta: dict[str, Any], tier: Tier = 'warn') -> ValidationResult
ValidationResult contains three fields:
| Field | Type | Meaning |
|---|---|---|
accepted |
bool |
Whether the selected tier accepts the metadata |
warnings |
list[str] |
Non-blocking missing-field and value findings |
errors |
list[str] |
Blocking strict-tier findings |
Validation tiers¶
| Tier | Behavior |
|---|---|
warn |
Records missing required fields and invalid values as warnings; always sets accepted=True |
strict |
Records missing or invalid required fields as errors and sets accepted=False; invalid optional fields remain warnings |
non_cccs |
Skips every check and returns accepted=True with empty warning and error lists |
Strict-tier ingest drops a rule when its entity is not marked strict. Warn-tier
ingest keeps the rule and records the findings in YaraRule.extra.
Fields required by ZettelForge strict mode¶
ZettelForge combines two CCCS optionality classes into one strict gate. It
requires every optional: No field and every optional: Optional field because
the local validator does not generate missing values.
| Field | CCCS optionality | Implemented format or values |
|---|---|---|
id |
Optional |
Base62 text with at least 16 characters |
fingerprint |
Optional |
40–64 hexadecimal characters |
version |
Optional |
Two numeric components, such as 1.0 |
modified |
Optional |
YYYY-MM-DD text |
status |
No |
TESTING, RELEASED, or DEPRECATED |
sharing |
No |
One of the supported TLP strings below |
source |
No |
Any non-empty value; uppercase is a convention, not a validation rule |
author |
No |
Whole value matches [A-Za-z0-9_.@+-]+ |
description |
No |
Non-empty after trimming |
category |
No |
INFO, EXPLOIT, TECHNIQUE, TOOL, or MALWARE |
Supported sharing values are:
TLP:CLEARandTLP:CLEAR//COMMERCIALTLP:WHITEandTLP:WHITE//COMMERCIALTLP:GREENandTLP:GREEN//COMMERCIALTLP:AMBERandTLP:AMBER//COMMERCIALTLP:AMBER+STRICTandTLP:AMBER+STRICT//COMMERCIAL
TLP:WHITE remains accepted alongside TLP:CLEAR for compatibility with
existing rules.
Optional fields with implemented validation¶
| Field | Accepted value |
|---|---|
date |
YYYY-MM-DD text |
malware_type |
One of the values in Malware type values |
actor_type |
APT, CRIMEWARE, or FIN |
hash |
32-, 40-, or 64-character hexadecimal value |
mitre_att |
One TA####, T####, T####.###, M####, G####, or S#### identifier |
An invalid optional value produces a warning in both warn and strict mode;
it does not make strict validation fail. The entity converter separately splits
comma- and semicolon-delimited mitre_att text, but the metadata validator
accepts only one identifier per value.
Declared fields without implemented validation¶
The vendored schema declares validators for these optional fields, but ZettelForge 2.8.0 accepts their values without applying those validators:
| Field | Format declared by the vendored schema |
|---|---|
score |
Integer percentage |
minimum_yara |
x.x version |
actor |
Actor name |
mitre_group |
ATT&CK group alias |
report |
Linked report |
reference |
Free-form external reference |
| category child metadata | Category-specific uppercase value |
The schema also declares unvalidated fields such as tags, vol_script,
al_*, credit, and original_*. Unknown metadata keys pass through without a
warning.
Malware type values¶
The implemented malware_type validator accepts:
ADWARE, APT, BACKDOOR, BANKER, BOOTKIT, BOT, BROWSER-HIJACKER,
BRUTEFORCER, CLICKFRAUD, CRYPTOMINER, DDOS, DOWNLOADER, DROPPER,
EXPLOITKIT, FAKEAV, HACKTOOL, INFOSTEALER, KEYLOGGER, LOADER,
OBFUSCATOR, POS, PROXY, RAT, RANSOMWARE, REVERSE-PROXY, ROOTKIT,
SCANNER, SCAREWARE, SPAMMER, TROJAN, VIRUS, WIPER, WEBSHELL, and
WORM.
Parser API¶
parse_yara¶
Signature: parse_yara(text: str) -> list[dict[str, Any]]
parse_yara parses source text into one dictionary per rule. It protects a
module-level plyara.Plyara instance with _PARSER_LOCK, clears the parser
before and after every call, flattens Plyara's metadata list into meta, and
carves raw_rule from the source line range.
The normalizer always adds these keys:
| Key | Type | Meaning |
|---|---|---|
meta |
dict |
Flattened metadata; the last duplicate key wins |
tags |
list[str] |
Inline tags, or an empty list |
raw_rule |
str |
Source text between Plyara's start and stop lines |
Plyara supplies the remaining rule-dependent keys, including rule_name,
metadata, strings, condition_terms, raw_condition, raw_strings,
raw_meta, start_line, stop_line, and scopes. ZettelForge also copies
parser-level imports into a rule when Plyara omits them from that rule.
Plyara syntax failures surface as ValueError with a plyara parse error
message.
parse_file¶
Signature: parse_file(path: str | Path) -> list[dict[str, Any]]
parse_file stats the path, rejects files larger than
MAX_RULE_FILE_BYTES = 1_048_576, reads UTF-8 text, and delegates to
parse_yara.
| Failure | Exception |
|---|---|
| Path cannot be statted | YaraParseError |
| File exceeds 1,048,576 bytes | YaraParseError |
| Plyara rejects the text | ValueError |
| UTF-8 read fails after a successful stat | The underlying read or decode exception |
YaraParseError extends ValueError.
Entity model¶
DetectionRule¶
YaraRule extends zettelforge.detection.base.DetectionRule.
| Field | Type | YARA mapping |
|---|---|---|
rule_id |
str |
CCCS id, or yara_ plus the first 16 content-hash characters |
title |
str |
Parsed rule name |
source_format |
str |
yara |
content_sha256 |
str |
SHA-256 of raw_rule, or a stable fallback projection |
description |
str \| None |
CCCS description |
author |
str \| None |
CCCS author |
date |
str \| None |
CCCS date |
modified |
str \| None |
CCCS modified |
references |
list[str] |
One CCCS report value when present; reference is not mapped |
tags |
list[str] |
Raw inline YARA tags |
level |
str \| None |
Remains None |
status |
str \| None |
Lowercase CCCS status |
tlp |
str \| None |
CCCS sharing value |
license |
str \| None |
Remains None |
source_repo |
str \| None |
Remains None |
source_path |
str \| None |
Remains None; directory ingest writes the path into the note body |
extra |
dict[str, Any] |
Validation and parser details described below |
YaraRule¶
| Field | Type | Mapping |
|---|---|---|
cccs_id |
str \| None |
CCCS id |
fingerprint |
str \| None |
CCCS fingerprint |
category |
str \| None |
CCCS category |
technique_tag |
str \| None |
CCCS technique |
cccs_version |
str \| None |
String form of CCCS version |
hash_of_sample |
list[str] |
One or more CCCS hash values |
rule_name |
str \| None |
Parsed rule name |
is_private |
bool |
Remains False in 2.8.0 even when Plyara reports a private scope |
is_global |
bool |
Remains False in 2.8.0 even when Plyara reports a global scope |
imports |
list[str] |
Parsed YARA module imports |
condition |
str \| None |
Trimmed raw condition |
extra always contains cccs_compliant, cccs_warnings, cccs_errors,
condition_terms, and source_line_range. It also contains source and
malware_type when present. If metadata supplies actor_type without an
actor name, extra contains actor_type and the converter emits no actor
relation.
Entity conversion¶
Signature:
rule_to_entities(rule: dict[str, Any], *, tier: str = 'warn') -> tuple[YaraRule, list[dict[str, Any]]]
The function validates rule['meta'] at the requested tier and returns a
YaraRule plus relation dictionaries. It returns the entity even when strict
validation fails; the ingest layer decides whether to drop it.
zettelforge.yara.entities.from_rule_dict(rule) is a compatibility wrapper that
calls rule_to_entities at the default warn tier. It is not a package-level
export.
Each relation dictionary contains:
| Key | Value |
|---|---|
from_type |
YaraRule |
from_value |
Source rule identifier |
rel |
ZettelForge relation name |
to_type |
Target entity type |
to_value |
Target entity identifier |
properties |
Relation metadata |
Emitted relations¶
| Relation | Target type | Emitted when |
|---|---|---|
detects |
AttackPattern |
CCCS mitre_att contains a token, including TA, T, M, G, or S identifiers |
detects |
AttackPattern |
An inline tag resolves as a technique |
attributed_to |
ThreatActor |
CCCS actor contains a name |
tagged_with |
YaraTag |
CCCS technique contains a value |
tagged_with |
YaraTag |
An inline tag resolves as a category or free-form tag |
references_cve |
Vulnerability |
An inline tag resolves as a CVE |
The CCCS mitre_att mapper uppercases and splits list, comma-delimited, and
semicolon-delimited values. It maps every token to AttackPattern; it does not
distinguish group, software, or tactic identifiers.
Tag resolution¶
Signature:
resolve_yara_tag(tag: str) -> tuple[str, dict[str, str]]
| Input pattern | Returned entity and properties |
|---|---|
T1218, attack.T1218, or attack_T1218 |
AttackPattern with technique_id='T1218' |
attack.T1059.003 |
AttackPattern with technique_id='T1059.003' |
CVE_2021_44228 or CVE-2021-44228 |
Vulnerability with canonical cve_id |
| Known category token | YaraTag with namespace category and an uppercase name |
| Any other value | YaraTag with namespace freeform and the trimmed input |
Known category tokens are APT, CRIME, CRIMEWARE, EXPL, HKTL, MAL,
MALWARE, PUA, RAT, RANSOM, RANSOMWARE, SUSP, VULN, and WEBSHELL.
Matching is case-insensitive.
Each inline tag emits one relation. A technique or CVE tag emits its typed relation instead of an additional raw-tag relation.
Ingest API¶
ingest_rule¶
Signature:
ingest_rule(rule_source, mm=None, *, domain='detection', tier='warn', sync=True) -> tuple[MemoryNote | None, list[dict]]
| Parameter | Accepted value | Default | Behavior |
|---|---|---|---|
rule_source |
Dictionary, YARA text, path string, or Path |
required | Normalizes the input and ingests only the first parsed rule |
mm |
MemoryManager-compatible object |
None |
Raises ValueError when omitted or None |
domain |
string | detection |
Stored note domain |
tier |
strict, warn, or non_cccs |
warn |
Controls CCCS validation |
sync |
boolean | True |
Controls inline enrichment in MemoryManager.remember |
Strict rejection returns (None, relations). A new accepted rule becomes a
memory note with source_type='yara'. The generated source reference is
yara:<rule_id>:<first-12-content-hash-characters>.
If the store already contains that source reference, the function returns the existing note and newly computed relations without persisting a duplicate.
ingest_rules_dir¶
Signature:
ingest_rules_dir(path, mm=None, *, glob='**/*.yar', tier='warn', domain='detection', bulk=False, flush_timeout=None) -> dict[str, Any]
The return dictionary has ingested, skipped, and errors fields. A missing
root returns {'ingested': 0, 'skipped': 0, 'errors': ['path does not exist: ...']}
instead of raising.
The default glob checks both .yar and .yara files. A custom glob replaces
that pair. The function records parse and per-rule failures in errors, counts
strict rejections and idempotent hits as skipped, and continues the walk.
When bulk=True, the function calls each rule ingest with sync=False and
calls mm.flush(timeout=flush_timeout) once after the walk when mm exposes a
flush method. A false flush result produces a warning log and does not add a
return error.
Directory security controls¶
| Control | Behavior |
|---|---|
| File size | parse_file rejects files larger than 1,048,576 bytes |
| Symlinks | ingest_rules_dir skips symlinked files |
| Root containment | ingest_rules_dir skips resolved paths outside the requested root |
Knowledge-graph edge persistence¶
Before persistence, ingest adds these properties unless the relation already supplies them:
| Property | Value |
|---|---|
edge_type |
detection |
source |
yara_ingest |
If one edge write raises an exception, ZettelForge logs
yara_edge_persist_failed and continues with the next relation.
CLI¶
The verified dry-run invocation against the v2.8.0 release fixture is:
python3 -m zettelforge.yara.ingest tests/fixtures/yara/technique_loader.yar --dry-run
| Argument | Default | Description |
|---|---|---|
path |
required | One .yar file or a directory |
--tier |
warn |
strict, warn, or non_cccs |
--dry-run |
off | Parses, validates, and maps without persistence |
--domain |
detection |
Memory domain for persisted notes |
--json |
off | Emits JSON instead of the human-readable summary |
The successful command writes this stdout and exits 0:
[warn] MemoryModule (technique_loader.yar) relations=2 mitre=T1218
warn: missing required CCCS field: id
warn: missing required CCCS field: fingerprint
warn: missing required CCCS field: modified
The ingest module invocation emits a non-fatal runpy warning on stderr
because zettelforge.yara.__init__ imports ingest before Python executes it as
a module. The command still runs.
Dry-run mode returns exit 1 for parse failures and strict-tier rejection. Live
mode returns exit 1 when the result contains errors or the input path is
missing. In 2.8.0, a strict-tier live rejection increments skipped but leaves
errors empty, so the command prints ingested=0 skipped=1 errors=0 and
incorrectly exits 0.
The CLI does not invoke the LLM rule explainer in 2.8.0.
Quick reference example¶
This self-contained example parses a rule and inspects its entity relations without writing data:
from zettelforge.yara import parse_yara, rule_to_entities
yara_text = """rule CommandShell : APT T1218 CVE_2021_44228 {
strings:
$cmd = "cmd.exe"
condition:
$cmd
}
"""
rule_dict = parse_yara(yara_text)[0]
entity, relations = rule_to_entities(rule_dict, tier="non_cccs")
print(entity.rule_id, entity.rule_name, entity.extra["cccs_compliant"])
print([(edge["rel"], edge["to_value"]) for edge in relations])
Output:
yara_bb08e39bbce9a1f8 CommandShell non_cccs
[('tagged_with', 'APT'), ('detects', 'T1218'), ('references_cve', 'CVE-2021-44228')]
Current limitations¶
These ZettelForge 2.8.0 behaviors are in progress:
- Plyara records private and global modifiers in
scopes, but entity conversion leavesYaraRule.is_privateandYaraRule.is_globalfalse. - The optional CCCS validators listed under Declared fields without implemented validation do not run.
- The metadata validator accepts one
mitre_attidentifier, while entity conversion also splits compound values. The converter maps every resulting identifier type toAttackPattern. YaraRule.referencesmapsreportbut ignoresreference.- Strict-tier rejection in live CLI mode exits 0 when no separate ingest error occurs.
- CLI dry-run directory traversal follows matching symlinked rule files; the
ingest_rules_dirpersistence path skips them. YaraValidationErroris exported but no YARA 2.8.0 public function raises it.- The CLI parses, validates, maps, and persists rules, but it does not run the LLM rule explainer.