Repository navigation
Identify files with custom rules before the built-in rules - #1530
Open
ebursztein wants to merge 10 commits into
Open
ebursztein wants to merge 10 commits into
ebursztein wants to merge 10 commits into
Conversation
Coverage Report for CI Build 37950811830Coverage increased (+0.005%) to 96.841%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
ia0
previously approved these changes
Oct 9, 2026
1 of 7 tasks
The GPU qualification test only compiles on macOS or with CUDA, so CI did not see that it still used Features.0 and the old FileType::convert signature. It now passes the options, with rules off since the reference outputs are the model's alone, and maps RulesVeto as unreachable.
Features extracted with use_rules = false recorded an empty list of matching rules, so a session with rules on vetoed every prediction whose rules have no false negatives: real WAV, PSD, Parquet and XLSX files came out unknown. Features now record None when the rules did not run, and the veto needs evidence that they ran and did not match.
Errors were the parser's debug output with byte spans, such as Span(13..13). Custom rules (next commit) make these errors user-facing, so each now reads "line L, column C: message".
Options::custom_rules holds compiled Rules (an Arc, so options stay cheap to clone), built with Rules::compile or Rules::from_files, or set with Builder::with_custom_rules. Extraction runs them first, then the built-in rules when use_rules is set, then the model: - custom rules identify a file when the ones that match agree on one content type; - built-in rules identify it when no custom rule matched and the ones that match agree; - otherwise the model decides, and a rule set that ran vetoes a content type it claims to never miss (every enforced rule of it is class "full") when none of its rules matched. Both sets share one read of a zip archive's tail. Features keep what matched and which rule sets ran, so the veto follows the rules used at extraction rather than the session's options. Custom rules use the validation of magika-rules, must label Magika content types, and must enforce at least one rule.
--rules-file (repeatable) passes custom rules to the library, which checks them before the built-in rules whatever --rules is; --rules-check validates them and prints the content types they identify. tests_data/rules/custom.yar enforces the PNG signature, which the built-in rules leave to the model, so the CLI, Python and C tests can share one custom rule.
Magika(rules=...) takes YARA text and Magika(rules_files=[...]) paths; invalid rules raise ValueError naming the line, file or rule. The tests share tests_data/rules/custom.yar with the CLI and check identify_path, identify_paths and identify_bytes, the veto of a full rule that does not match, and the errors.
magika_rules_new compiles YARA text into an opaque MagikaRules, writing the error message into a caller buffer on MAGIKA_STATUS_INVALID_RULES, and magika_rules_free releases it. MagikaOptions gains custom_rules; the options keep their own reference, so the caller may free the rules once the call returns. The test identifies the PNG sample by rules alone only with tests_data/rules/custom.yar. The header is edited by hand, as cbindgen would write it.
ebursztein
force-pushed
the
custom-rules
branch
from
October 9, 2026 14:50
ff2cdbf to
93c9b63
Compare
ia0
enabled auto-merge (squash)
October 9, 2026 15:15
ia0
disabled auto-merge
October 9, 2026 15:15
ia0
approved these changes
Oct 9, 2026
ia0
enabled auto-merge (squash)
October 9, 2026 15:22
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1537.
Stacked on #1529: the first commit (
d96c7422, "Veto ML output when rules have no false negatives") is #1529 itself. Once #1529 merges, this branch is rebased and that commit disappears.Two fixes to #1529, at the bottom of the stack
They are separate commits so they can move into #1529 or land here, whichever is easier.
Fix the GPU tests for the rules veto (#1529):rust/lib/src/tests/gpu.rsonly compiles on macOS or with CUDA, so CI didn't see that it still usedFeatures.0and the oldFileType::convertsignature. It now passes the options with rules off, since the reference outputs come from the model alone. The ignored GPU qualification test passes on an Apple GPU.Veto only when the rules ran (#1529): features extracted withuse_rules = falserecorded an empty list of matching rules. A session with rules on then vetoed every prediction of a content type whose rules have no false negatives. Through the public API (extract_filewith rules off, thenidentify_featureson a default session), real WAV, PSD, Parquet and XLSX samples fromtests_data/basiccame outunknown. The C API, which takes extraction options separately, hits this directly. Features now recordNonewhen no rules ran. In this PR, they record which rule sets ran.Custom rules
Options::custom_rules: Option<Rules>.Rulesis anArc, soOptionsstays cheap to clone. Extraction (FeaturesOrRuled::extract_*) applies the rules, so custom rules work in any pipeline built on the low-level API, not only in our front ends.Order (each step only if enabled):
use_rules);use_model).When each step decides:
Veto: custom rules use #1529's mechanism. A content type whose enforced custom rules are all
class = "full"(fn_rate = 0) vetoes a model prediction of it when none of them matched;partialdoesn't veto. The veto follows the rule sets that ran at extraction, whichFeaturesrecords, not the options of the session that runs the model.Shared with the built-in rules:
magika-rules);Labels must be Magika content types, and a file must enforce at least one rule.
Name the line and column of YARA syntax errorsmagika-rulesreported the parser's debug output with byte spans (Span(13..13)). It now reportsline 2, column 9: expecting \condition`, found end of file`.Identify with custom rules before the built-in rulesmagika::Rules(compile,from_files,content_types),Options::custom_rules,Builder::with_custom_rules.from_filesvalidates each file alone, so an error names its file and line.Add --rules-file and --rules-check to the CLI--rules-file PATH(repeatable, hidden and experimental like--rules) and--rules-check, which validates the files and prints their content types. Addstests_data/rules/custom.yar, shared by the CLI, Python and C tests: it enforces the PNG signature, which the built-in rules leave to the model.Add custom rules to the Python Magika classMagika(rules=...)andMagika(rules_files=[...]). Invalid rules raiseValueError.Add custom rules to the C librarymagika_rules_new/magika_rules_free, an opaqueMagikaRules,MagikaOptions.custom_rules, andMAGIKA_STATUS_INVALID_RULESwith the message written to a caller buffer. The options keep their own reference. The header is edited by hand, as cbindgen would write it.Document custom rules in the rules READMENumbers
Apple M-series, release build. Extraction runs over the 101 in-memory files of
tests_data/basic, best of 20 runs.fde2d0ab)Unset custom rules cost nothing measurable; the two runs without them differ only by noise on a loaded machine.
Testing
docxsample, which needs the tail read;test.sh:--rules-checkoutput; PNG isunknownwith--rules=onlyandpngonce the custom file is given, also with--rules=off; a broken file is reported with its line.identify_path,identify_pathsandidentify_bytes; the veto (RULES_VETO); the errors. The suite has 40 passing tests, and ruff and mypy are clean.test.sh: the same PNG check through the C API, freeing the rules before use, plus invalid rules with and without an error buffer. It passes under ASan and UBSan with gcc and clang, static and shared.rust/test.shpasses on stable and nightly (48 test suites). Its final sync check only flagsmodel.probe.f32le, which regenerates differently on this Mac, as it does onmain.rust/changelog.shpasses.🤖 Generated with Claude Code