working-with-filters
GitHub用于创建、编辑和调试 UTMStack 事件处理器过滤器(YAML 管道),解决字段缺失、规则匹配失败及部署回退等问题。核心在于理解原始日志到标准化 Event/Side 的转换流程,处理 CEL 类型限制、重命名顺序及 Proto 协议约束。
Trigger Scenarios
Install
npx skills add utmstack/UTMStack --skill working-with-filters -g -y
SKILL.md
Frontmatter
{
"name": "working-with-filters",
"description": "Use when creating, editing, debugging, or deploying UTMStack event-processor filters (the YAML pipelines under filters\/ that normalize vendor logs into the go-sdk Event\/Side schema), or when a filter field is missing, a rule cannot match a value, actionResult is wrong, or a dropped op needs to be kept. Typical prompts are edit the o365 filter, add a rename or cast step, why is log.Parameters not matching, deploy the filter, or the filter reverted after a restart. Not for correlation rules, use working-with-rules for those."
}
Working with UTMStack Filters
Filters are YAML in filters/<vendor>/<name>.yml — a pipeline of ordered steps that turn a vendor's raw log into the canonical Event + Side (origin/target) shape. Rules then match the normalized field, never the raw one.
The single most important mental model: a filter field only exists if (1) it is a valid go-sdk proto path AND (2) a preceding step actually populates it. Unknown top-level paths are silently dropped at proto conversion — no error, the field just never appears.
Core workflow (always this order)
- Read the raw shape first. Before writing a step, query the live index to see the exact fields a real event carries and their JSON types (string vs array vs int). See
references/deploy-and-durability.mdfor the no-JWT query snippet. Guessing field names is how filters go dead. - Edit
filters/<vendor>/<name>.yml. - Validate with PyYAML, never the LSP. The LSP throws false
All sequence items must start at the same column/Implicit map keyerrors on these files. The source of truth:python -c "import yaml; d=yaml.safe_load(open('filters/office365/o365.yml',encoding='utf-8')); print(len(d['pipeline'][0]['steps']))"(use the utmstack venv python, setPYTHONIOENCODING=utf-8). - Deploy to all three layers or it reverts on restart. See
references/deploy-and-durability.md. - Verify the engine actually reloaded it (
/workdir/pipeline/filters/<id>.yaml), not just the DB.
Critical gotchas (the ones that cause dead detections)
- CEL is scalar-only.
contains()/startsWith/endsWithrequire the gjson value to be a String;equals/oneOfcompare scalars. Array/object fields (log.Parameters,log.Members,Target) return false. If a rule mustcontainsone of them, add acast: {fields:[...], to: string}step — butcaston an array stringifies it, socontainsthen works. Confirm ingo-sdk .../plugins/cel_overloads.go. actionis a RENAME, not a raw field. In O365,log.Operation → action. Adrop/renamekeys onactiononly work after that rename; key onlog.Operationif you move them before it. Renames are lossy — the source is deleted.actionResultis ADDED by the filter, derived fromlog.ResultStatus. Order ofaddsteps matters (lateraddwins for a matching event), and some vendors lie (AAD reportsResultStatus: Successon a failed login → force-override tofailed).dropis a performance lever. Put thedropstep as early as possible (right afterjson) keyed on the raw op field, so dropped events skip all renames/adds and the geolocationdynamicplugin call. Reorder is safe iff the match set is identical.- Schema is proto-gated.
Sidehashost, nothostname;Eventhas nosystem.*. Writingorigin.hostnamesilently produces nothing.
Read these when you need them
references/cel-semantics.md— every CEL function + its exact type requirement (from go-sdk).references/deploy-and-durability.md— the 3-layer deploy, container re-lookup, restart-revert trap, filter-ID renumbering, no-JWT queries.references/known-mistakes.md— error classes from past campaigns + thedocs/rvald26-draft-prs-review.mdfindings, so you don't repeat them.
Cross-reference: the rule that consumes a field is the one to check when a filter change lands — see the working-with-rules skill.
Version History
- ab96ae9 Current 2026-09-23 05:30


