Skip to content

Sync s1-secops-skills: plugin 1.3.9, live-validated PowerQuery guidance and regression cases - #105

Merged
marcorottigni-s1 merged 1 commit into
Sentinel-One:mainfrom
pmoses-s1:sync/skills-1.3.9-powerquery-validated
Oct 2, 2026
Merged

marcorottigni-s1 merged 1 commit into
Sentinel-One:mainfrom
pmoses-s1:sync/skills-1.3.9-powerquery-validated

Conversation

@pmoses-s1

Copy link
Copy Markdown
Contributor

Sync s1-secops-skills: plugin 1.3.9, live-validated PowerQuery guidance

This is a skills-only sync. The MCP and Docker image are unchanged: the image stays at sentinelone/secops-mcps:1.4.8 and the MCP at 1.3.9.

What changed

The powerquery skill now documents only behaviour that was measured on a live console. Each statement points to a regression case in skills/powerquery/tests/live/pq_live_cases.json. All 68 cases pass.

  • Subqueries

    • The inner column may be an alias, so you can match against a different field.
    • On a let-defined field, a subquery returns 0 rows with no error.
    • If the inner query is empty, the negated form returns every row, so an empty allowlist excludes nothing.
    • Subqueries never match null, but join does.
    • in:anycase works.
    • any(a, b) in (subquery) is rejected.
    • matchCount includes the events the inner query scanned.
    • Two single-field subqueries cannot allowlist pairs. The OR-of-ANDs pattern is documented as the workaround.
  • Operators and functions

    • Negation needs parentheses: NOT f contains 'x' silently returns 0 rows.
    • A bare | filter x is a text search, not a true/false test.
    • New coverage: starts_with / ends_with, any() / all() across several fields, and triple-quoted strings.
    • Comparing text to numbers is lenient before group and strict after it.
    • Also covered:
      • intersect_estimate_distinct
      • type()
      • .expand() multiplies row counts
      • top limits
      • compare column naming
  • Lookups

    • Duplicate keys: the first row wins and rows are not multiplied.
    • Wildcard and CIDR matching rules: the most specific CIDR wins, and a non-IP value gives null.
    • The by direction is enforced.
    • Hyphenated table names work unquoted. This corrects an older claim.
    • dataset output accepts ordinary pipeline commands.
    • savelookup writes JSON or CSV depending on the name, and its 'merge' behaviour is now documented.
    • Spaces in a CSV header become part of the column name.
    • dataset error codes are documented.
  • Detection rules

    • matchesRequired must be between 1 and 1000.
    • matchInOrder follows the order events arrive, not their timestamps.
    • entity is required, and entity: "none" works.
    • A scheduled rule without | group runs at the interval you request.
    • dataset, savelookup and CIDR/wildcard lookups are rejected in scheduled rules; the exact errors are documented.
    • The cool-off setting is at data.coolOffSettings.renotifyMinutes.
    • A requested Active status is honoured; the rule reaches Active on its own in about 1 to 7 minutes.
    • A scheduled rule without | group alerted on 3 consecutive 10-minute runs and stayed silent on a run with no events.
    • Dedup (disableStreaksLogic, on by default) suppresses a result row that has already been alerted on. A grouped rule returning the same row raised one alert and then went silent, even though its windows still held data. With dedup off it alerted on every run.
    • Scheduled-rule restrictions apply only before the first | group:
      • rejected before the first group: now() / querystart(), CIDR and wildcard lookups, and lookups over 10,000 rows (exact error texts are documented);
      • rejected anywhere: dataset or datasource as the source, savelookup, a timebucket under 30s, and a piped body with no group;
      • after the first group, the same features are accepted and fire real alerts (verified with probe events), including an allow-list anti-join.
    • A correlation rule needs 2 sub-queries, or 1 with matchesRequired above 1.
  • Automatic lookups: the old unvalidated 100,000-row figure is replaced with limits measured when /automaticLookups is written (each over-limit write is rejected with HTTP 400):

    • 200 rows per table, and 200 across all tables;
    • 1 MB per table, and 5 MB in total;
    • 10 output fields per spec, and 50 across all specs.

    Enrichment is applied at query time, and the output field can be used as a filter.

  • Tests

    • A new offline test, skills/powerquery/tests/test_live_cases.py, checks that:
      • every case is structurally valid;
      • cases and fixtures carry no tenant data (no hosts, users, IPs, emails or console URLs);
      • fixtures are synthetic;
      • every case cited in the docs exists.
    • The live runner itself stays in the source repo.

Checks

  • sync-to-ai-siem.sh verify: identities match 1.3.9, no real tenant identifiers, no new broken links, no runtime artefacts, no dangling symlinks.
  • python3 -m unittest discover -s plugins/s1-secops-skills/skills/powerquery/tests: OK.
  • The plugin was rebuilt and dist/s1-secops-skills-v1.3.9.plugin replaces 1.3.8.

@marcorottigni-s1 marcorottigni-s1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved

@marcorottigni-s1
marcorottigni-s1 merged commit fd59e15 into Sentinel-One:main Oct 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants