Skip to content

Complete detection-quality edge-case acceptance coverage - #260

Merged
sodejm merged 1 commit into
mainfrom
codex/issue-194-detection-edge-cases
Oct 11, 2026
Merged

sodejm merged 1 commit into
mainfrom
codex/issue-194-detection-edge-cases

Conversation

@sodejm

@sodejm sodejm commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

Complete the remaining acceptance coverage in #194 for the offline detection quality workbench. Add scoped reference semantics and fixtures for cross-tenant joins, null required fields, timestamp boundaries, duplicate events, and prior-alert suppression. The bundled Sentinel query remains unchanged; the bundled Splunk rule declares no suppression because its source has no tenant identifier.

Closes #194.

Acceptance criteria

  • Fixtures and tests cover all five edge cases.
  • The evaluator produces deterministic scoped outcomes and metrics for those cases.
  • Focused checks and make check passed; affected user and operator docs were updated.
  • Out of scope: live Sentinel or Splunk execution, production telemetry, and deployment.

Evidence

  • Focused validation passed: venv/bin/python -m pytest -q tests/test_workbench.py tests/test_edge_cases.py in the package.
  • make check passed in this worktree. The local push gate also passed at commit 92bce974b0b6618e9ddc7ecdd39486073fe5c98b, including Ruff 0.16.3, Semgrep 1.179.0, and dependency audit.
  • Hosted CI state will be checked on this PR's exact head.

Impact review

  • Tests: edge fixtures and regression assertions cover required semantics.
  • Documentation: README, playbook, and review skill explain evaluator limits and report shape.
  • Architecture or migrations: report schema advances to cops.detection-quality-report/v2; readers of v1 need to handle schema-error counts and nullable precision/recall.
  • Security, privacy, and abuse cases: joins and suppression require non-null tenant scope to avoid cross-tenant matches; tests use inert local telemetry.
  • Operations, release, and rollback: no deployment; rollback is a revert before release.

Critique disposition

Independent automated review identified that the original Splunk suppression metadata could not enforce tenant scope because the source has no TenantId. The rule now declares no suppression, and a regression fixture verifies that a same-host prior alert does not suppress it. The final diff was self-reviewed; automated review does not satisfy the required human review.

Risks and residual uncertainty

The reference evaluator models fixture outcomes and does not execute KQL or SPL or query alert history. Target-SIEM execution and production data behavior remain unverified. Human exact-head review is required before merge.

@sodejm
sodejm marked this pull request as ready for review October 11, 2026 15:50
@sodejm
sodejm merged commit 9f97fa6 into main Oct 11, 2026
18 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T15:57:04.806508Z 92bce97 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92bce974b0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +148 to 149
if ev.get(req) is None:
missing_fields_in_case.add(req)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve false positives in mixed-schema cases

When a negative case contains multiple primary events—one with a null required field and another complete event that matches—the case-wide null collection classifies the entire result as missing_field_error even though actual_match is true. The suite then excludes it from false positives and treats expected-negative schema errors as passing, allowing a noisy rule to clear the gate; preserve the false-positive outcome whenever an evaluable event matched, or track schema and match outcomes independently.

AGENTS.md reference: plugins/AGENTS.md:L9-L13

Useful? React with 👍 / 👎.

Comment on lines +45 to +48
if not any(
secondary.get("source") in secondary_sources
and all(event.get(key) is not None and event.get(key) == secondary.get(key) for key in join_keys)
for secondary in case.events

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require every declared secondary source to join

When a rule declares multiple secondary sources and inner joins, any(...) admits a primary event as soon as one secondary source matches the keys. A fixture missing another required secondary row can therefore be reported as a match even though the actual sequence of inner joins would reject it; group matching rows by source and require a match for each declared joined source.

AGENTS.md reference: plugins/AGENTS.md:L9-L13

Useful? React with 👍 / 👎.

for case in cases:
for event in case.events:
present_keys = set(event.keys())
if event.get("source") not in (None, rule.source_data["primary_source"]):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate primary_source before dependency analysis

A rule whose source_data omits primary_source is still accepted by Rule.from_dict and the CLI validate command, but any nonempty fixture now reaches this direct lookup and raises an uncaught KeyError because the CLI only handles QualityError. Validate the nested source contract during rule loading, or convert this failure into a controlled validation error, so a rule reported as valid cannot crash evaluate or test-suite.

Useful? React with 👍 / 👎.

Comment on lines +119 to +121
def test_time_window_is_inclusive_and_excludes_future_events(rule: Rule, event_time: str, expected: bool) -> None:
fixture = case("window", (sign_in(TimeGenerated=event_time),), expected, evaluation_time="2026-10-01T12:00:00Z")
assert evaluate_fixture_case(fixture, rule).actual_match is expected

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add executable scenarios for evaluator semantics

This commit introduces behavioral requirements for joins, null telemetry, time boundaries, deduplication, and suppression only as package-level pytest tests; no corresponding specs/ feature or executable Gherkin scenario is added. Add matching pytest-bdd scenarios and step definitions so these acceptance criteria participate in the repository's required specification gate rather than existing only as implementation tests.

AGENTS.md reference: AGENTS.md:L162-L163

Useful? React with 👍 / 👎.

Comment on lines +74 to +77
tenant_id = event.get("TenantId")
if not isinstance(tenant_id, str) or not tenant_id or tenant_id != alert.get("tenant_id"):
continue
if earliest <= alert_time <= event_time:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update the threat model for tenant-scoped suppression

The new prior-alert path makes tenant- and identity-scoped decisions that can suppress otherwise matching detections, but the detailed threat model remains unchanged despite this new security-relevant control and its missing-tenant assumptions. Update its data-flow evidence, tenant-confusion abuse cases, controls, validation, and unresolved assumptions, or record the required specific no-impact rationale if the model truly remains unchanged.

AGENTS.md reference: AGENTS.md:L187-L191

Useful? React with 👍 / 👎.

Comment on lines +154 to +157
"case_id": "CASE-SUPPRESSED-ALERT",
"description": "A prior alert for the same user and tenant suppresses a repeated match",
"label": "negative",
"expected_match": false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude suppressed attack repeats from true negatives

This fixture contains an event that satisfies the malicious sign-in predicate and is omitted only because a prior alert suppresses a duplicate notification, but labeling it negative makes the evaluator count it as a true negative that the report describes as benign activity. The bundled TN total therefore mixes benign telemetry with detected-but-suppressed attacks; introduce a separate suppression outcome outside the confusion matrix, or otherwise keep detection truth separate from notification emission.

AGENTS.md reference: plugins/AGENTS.md:L9-L13

Useful? React with 👍 / 👎.

Comment on lines 79 to +81
"identity_scope": {
"type": "string"
"type": "string",
"enum": ["global", "user", "host"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Version the narrowed identity_scope contract

The previous v1 schema accepted any string for identity_scope, while this enum and the corresponding parser check now reject existing v1 documents that use another scope—even when suppression is disabled—without changing the cops.detection-rule/v1 discriminator. Bump the rule contract and provide a migration path, or preserve v1 parsing compatibility and apply the narrower validation only under a new schema version.

AGENTS.md reference: AGENTS.md:L132-L134

Useful? React with 👍 / 👎.

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.

Complete detection-quality edge-case acceptance coverage

1 participant