Skip to content

feat: correlation rule improvements and platform enhancements#70

Merged
TerrifiedBug merged 25 commits intomainfrom
feat/platform-improvements-v3
Jan 30, 2026
Merged

feat: correlation rule improvements and platform enhancements#70
TerrifiedBug merged 25 commits intomainfrom
feat/platform-improvements-v3

Conversation

@TerrifiedBug
Copy link
Owner

Summary

  • Direct log field support for correlation rules - Users can now correlate on any common log field (e.g., user.name, source.ip) in addition to Sigma fields
  • Improved Sigma field selection - Dropdown now shows fields with valid mappings for both rules' index patterns, verified against actual OpenSearch indexes
  • Consistent searchable dropdowns - Both Sigma Field and Log Field modes use the same searchable Popover UI with tick marks
  • Bug fixes - Fixed page refresh issues when switching field types or selecting fields, fixed duplicate timestamps in grouped alerts

Changes

Correlation Rules

  • Add entity_field_type column to support "sigma" (default) and "direct" field types
  • Add GET /correlation-rules/common-log-fields endpoint for discovering common fields between index patterns
  • Add GET /correlation-rules/common-sigma-fields endpoint for finding Sigma fields with valid mappings for both indexes
  • Update correlation service to handle direct field resolution (bypasses Sigma field mapping)
  • Enhanced correlation logging for debugging

Frontend

  • Add field type toggle (Sigma Field / Log Field) in correlation rule editor
  • Both dropdowns now use searchable Popover UI with tick marks for selection
  • Field mappings display shows target fields for both rules
  • Fixed useEffect dependency issues causing page refreshes

Other

  • Fixed duplicate timestamp display in grouped alert headers
  • Added correlation rule debugging logs

Test plan

  • Create a correlation rule using Sigma Field mode - verify only mapped fields appear
  • Create a correlation rule using Log Field mode - verify common fields from both indexes appear
  • Switch between field types - verify no page refresh occurs
  • Select fields from dropdowns - verify selection works without issues
  • Save and deploy correlation rule - verify it triggers correctly

TerrifiedBug and others added 22 commits January 30, 2026 00:30
Navigate to edit mode instead of list after creating new correlation rule.
Matches existing behavior for regular rule editor.
- Block creation of exact duplicate exceptions (same field+value+operator)
- Warn when new exception overlaps with existing pattern
- Add warning field to RuleExceptionResponse schema
- Fix test database enum type creation in conftest.py
- Fix auth test to expect 401 instead of 403

Closes: Platform Improvements v3 - Task 2
- Add optional alert_id to exception create request
- Auto-set alert status to false_positive when exception created from it
- Store exception reference on alert document in OpenSearch
- Show exception badge with tooltip in alert detail view
- Reload alert after exception creation to show updated status
- New AlertComment model with soft delete support
- API endpoints: list, create, delete (admin only)
- Comments section in alert detail page
- Immutable for users, admins can delete
- Assign/unassign endpoints for alert ownership
- Owner filter in alerts list (use 'me' for current user)
- Take Ownership/Release buttons in alert detail
- 'Assigned to me' filter checkbox on alerts page
- 'My Alerts' quick link in sidebar navigation
- Fix ownership endpoints to use correct OpenSearch document ID (hit["_id"])
- Add owner_id, owner_username, owned_at to AlertResponse schema
- Add exception_created and ti_enrichment to AlertResponse schema
- Remove "My Alerts" nav link from Header (use filter on Alerts page instead)
- Add Owner column to alerts table showing "Unassigned" for unassigned alerts
Dynamic mapping creates text+keyword multifield for strings. Term queries
need to use .keyword suffix for exact matching.
Implement alert clustering feature that groups related alerts by
rule_id and entity (host, user, IP) within a configurable time window.
This reduces alert fatigue by consolidating repetitive alerts.

Backend changes:
- Add cluster_alerts() function with time-window based grouping
- Add extract_entity_value() helper for entity field extraction
- Add GET/PUT /settings/alert-clustering endpoints
- Update alerts list endpoint with clustering support
- Add AlertCluster and ClusteredAlertListResponse schemas

Frontend changes:
- Add Alerts tab in Settings with clustering configuration
- Update Alerts page with expandable cluster rows
- Add cluster selection and bulk actions support
- Display cluster count badges and time ranges

Tests:
- Add 20 test cases covering clustering logic and edge cases
Adds PDF export functionality to the ATT&CK Matrix page:

Backend (reports.py):
- New POST /api/reports/attack-coverage endpoint
- Generates PDF report with:
  - Summary statistics (total techniques, covered, coverage %)
  - Coverage breakdown by tactic
  - Top 10 detection gaps (uncovered techniques)
  - Top 10 best covered techniques
- Uses landscape letter format with styled tables

Frontend (AttackMatrix.tsx):
- Export PDF button in header with loading state
- Downloads PDF file with date-stamped filename
- Toast notifications for success/error feedback

Tests (test_attack_export.py):
- Tests for PDF generation (magic bytes verification)
- Authentication requirement test
- Test with actual technique/rule data
- Content-Disposition header verification
- Default format test
Remove entity_fields configuration from alert clustering feature.
Alerts are now clustered solely by rule_id within the time window,
making the feature simpler to configure and understand.

Changes:
- Remove entity_fields from AlertClusteringSettings schema
- Simplify cluster_alerts() to group by rule_id only
- Remove extract_entity_value() helper function (no longer needed)
- Update Settings UI to show only enable toggle and time window
- Update tests to reflect simplified grouping logic
Add flag_modified() call when updating Setting.value to explicitly
mark the JSONB column as modified. Without this, SQLAlchemy may not
detect changes to JSON columns and skip the UPDATE statement.
The /{key} catch-all route was matching /alert-clustering requests
before the specific endpoints could handle them, causing saves to
return {"success": true} instead of the proper response and not
using the correct schema validation.
Update alert clustering to include complete alert objects in the
response, not just IDs. This allows the frontend to display full
alert details (rule title, severity, status, owner, tags, timestamp)
when expanding a cluster, matching the non-clustered view.

Changes:
- Add alerts field to AlertCluster schema (backend and frontend)
- Include full alert data in _create_cluster function
- Update Alerts page to render full details in expanded cluster rows
- Add test assertion for alerts field in cluster output
When clustering is enabled, override the client's page size (typically 25)
and fetch up to 1000 alerts. This provides a much better clustering view
since pagination doesn't make sense with clustering - we need to see the
full picture to properly group alerts by time window.
- Strip .keyword and .text OpenSearch field type suffixes when extracting
  entity values from log documents (these are query hints, not document paths)
- Fix correlation alert payload to include all correlation data (first_alert_id,
  second_alert_id, rule IDs, timestamps) instead of missing source_alerts key
- Fix SQLAlchemy query to use scalars().first() instead of first() to get
  model object instead of Row
- Rewrite correlation tests to properly test the state machine logic with
  mocked field resolution
- Add clickable "View Alert" link to Discord/Slack webhook notifications
- Configure nginx to only log 4xx/5xx responses (reduce log noise)
- Wrap Health page Indexes section in Card for design consistency
- Persist "Assigned to Me" checkbox preference via localStorage
- Add "Take Ownership" bulk action button to Alerts table
Docker/GHCR requires repository names to be lowercase, but
github.repository may contain uppercase letters (e.g., TerrifiedBug/chad).

Add a step in the changes job to lowercase the repository name and
expose it as job outputs. All subsequent jobs now reference these
lowercased outputs instead of the direct env var.

Fixes build failures on main branch merges.
- Only show time range for grouped alerts when start/end differ by >1min
- Add INFO-level logging to correlation service for debugging:
  - Log when correlation rules are found for a rule
  - Log entity field resolution results
  - Log when correlation state is stored (waiting for paired rule)
Previously correlation rules could only use Sigma fields (fields used in
rule detection logic) for entity correlation. This limited real-world use
cases where users need to correlate on context fields like user.name,
source.ip, or host.name.

Changes:
- Add entity_field_type column to correlation_rules and versions tables
- Support "sigma" (default) and "direct" field types
- Direct mode bypasses Sigma field mapping, uses field path directly
- Add GET /correlation-rules/common-log-fields endpoint for field discovery
- Update CorrelationRuleEditor with field type toggle (Sigma Field / Log Field)
- Log Field mode shows searchable dropdown of fields common to both rules' indexes
- Add GET /correlation-rules/common-sigma-fields endpoint that returns
  Sigma fields with valid mappings for both rules' index patterns
- Verify mapped target fields actually exist in OpenSearch indexes
- Replace rule detection field intersection with field mapping lookup
- Make Sigma field dropdown use same searchable Popover UI as Log Field
- Fix page refresh issue by removing formData.entity_field from useEffect deps
- Add e.preventDefault() to field type toggle buttons for safety


# revision identifiers, used by Alembic.
revision: str = '09kr_add_entity_field_type'

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable 'revision' is not used.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.


# revision identifiers, used by Alembic.
revision: str = '09kr_add_entity_field_type'
down_revision: Union[str, None] = 'f42b74859833'

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable 'down_revision' is not used.

Copilot Autofix

AI about 1 month ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

# revision identifiers, used by Alembic.
revision: str = '09kr_add_entity_field_type'
down_revision: Union[str, None] = 'f42b74859833'
branch_labels: Union[str, Sequence[str], None] = None

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable 'branch_labels' is not used.

Copilot Autofix

AI about 1 month ago

To fix the problem, remove the unused global variable branch_labels since it is not referenced in the file and not clearly needed for Alembic in this simple migration. This aligns with the recommendation to delete unused assignments when they are not intentional documentation artifacts.

Concretely, in backend/alembic/versions/09kr_add_entity_field_type.py, delete the line that defines branch_labels: Union[str, Sequence[str], None] = None and leave the other Alembic identifier variables (revision, down_revision, depends_on) untouched. No additional imports or helper functions are required.

Suggested changeset 1
backend/alembic/versions/09kr_add_entity_field_type.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/alembic/versions/09kr_add_entity_field_type.py b/backend/alembic/versions/09kr_add_entity_field_type.py
--- a/backend/alembic/versions/09kr_add_entity_field_type.py
+++ b/backend/alembic/versions/09kr_add_entity_field_type.py
@@ -14,7 +14,6 @@
 # revision identifiers, used by Alembic.
 revision: str = '09kr_add_entity_field_type'
 down_revision: Union[str, None] = 'f42b74859833'
-branch_labels: Union[str, Sequence[str], None] = None
 depends_on: Union[str, Sequence[str], None] = None
 
 
EOF
@@ -14,7 +14,6 @@
# revision identifiers, used by Alembic.
revision: str = '09kr_add_entity_field_type'
down_revision: Union[str, None] = 'f42b74859833'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


Copilot is powered by AI and may make mistakes. Always verify output.
revision: str = '09kr_add_entity_field_type'
down_revision: Union[str, None] = 'f42b74859833'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable 'depends_on' is not used.

Copilot Autofix

AI about 1 month ago

In general, an unused global variable that has no side effects should be removed, unless it is intentionally unused and named to indicate that, or it is required by an external framework. Here, Alembic only needs depends_on when there is an actual dependency; setting it to None is equivalent to omitting it. The safest fix that does not change functionality is to delete the depends_on assignment line entirely.

Concretely, in backend/alembic/versions/09kr_add_entity_field_type.py, remove line 18:

depends_on: Union[str, Sequence[str], None] = None

No other changes are required—revision, down_revision, and branch_labels remain as they are, and no imports or additional definitions are needed.

Suggested changeset 1
backend/alembic/versions/09kr_add_entity_field_type.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/backend/alembic/versions/09kr_add_entity_field_type.py b/backend/alembic/versions/09kr_add_entity_field_type.py
--- a/backend/alembic/versions/09kr_add_entity_field_type.py
+++ b/backend/alembic/versions/09kr_add_entity_field_type.py
@@ -15,7 +15,6 @@
 revision: str = '09kr_add_entity_field_type'
 down_revision: Union[str, None] = 'f42b74859833'
 branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
 
 
 def upgrade() -> None:
EOF
@@ -15,7 +15,6 @@
revision: str = '09kr_add_entity_field_type'
down_revision: Union[str, None] = 'f42b74859833'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
Copilot is powered by AI and may make mistakes. Always verify output.
Add sanitize_log_value() to escape newlines and control characters
in user-controlled values before logging. Also disable postgres
checkpoint logs by default.
- alerts.py: Don't expose exception details to users, log internally
- Health.tsx: Set isLoading=true at start of loadHealth for spinner
User-facing latency displays now show seconds (e.g., "2.5s" instead of
"2500ms") for consistency with the settings UI which accepts seconds.
Backend still stores values in ms internally.
@TerrifiedBug TerrifiedBug merged commit 492c5c0 into main Jan 30, 2026
13 checks passed
@TerrifiedBug TerrifiedBug deleted the feat/platform-improvements-v3 branch January 30, 2026 12:13
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.

1 participant