Skip to content

SentinelToXDRMigration: qualification follow-ups, XDR static KQL override, and NPF metrics - #15474

Open
idoshabi07 wants to merge 7 commits into
Azure:masterfrom
idoshabi07:toolkit-publication-qualification-followups
Open

idoshabi07 wants to merge 7 commits into
Azure:masterfrom
idoshabi07:toolkit-publication-qualification-followups

Conversation

@idoshabi07

Copy link
Copy Markdown
Contributor

Required items, please complete

Change(s):

  • Implement Sentinel-to-XDR Migration Toolkit qualification follow-ups (Todos #0 through Azureadhunting #6):
    • Todo #0 (Security Gate & Test/Profile Isolation): Isolate Azure CLI profile checks (AZURE_CONFIG_DIR) in runtime consent and mock InteractiveBrowserCredential in ests/conftest.py.
    • Todo add cscript summary hunting query #1 (Live Rule Enablement & Alert Generation): Add --keep-enabled and �lertParityReady checks across Custom Detection and Analytic Rule deployment and alert parity.
    • Todo Main change is to the uncommon processes query. #2 (Strict V3.1 Packaging Verification): Verify XDR Detections (customDetectionRule_) and XDR Workbooks (workbooks_) in mainTemplate.json and propagate exit codes in createSolutionV3_1.ps1.
    • Todo Communityupdates #3 (Qualification Default Profile & Windows Fix): Flip default --workflow-profile from �uthoring to qualification and guard directory os.fsync on Windows in �udget.py.
    • Todo Useragentfix #4 (Static KQL Validation for XDR Artifacts): Add static KQL validation for XDR Detections/.yaml and XDR Workbooks/.json in solution_manager/authoring/validator.py, .script/local-validation/validate-xdr-content.py, and .script/tests/KqlvalidationsTests/KqlValidationTests.cs (with Defender XDR Advanced Hunting table schemas).
    • Todo Add two office hunting queries: the first shows new users created; the second user ids or user agents associated with multiple IPs within a short space of time. #5 (Qualification Mode Static KQL Override): Emit kqlRuntimeVerifiedRules SHA-256 query hashes in pr-qualification-result.json and downgrade semantic KQL errors (KS107/KS141) in KqlValidationTests.cs when live qualification passed.
    • Todo Azureadhunting #6 (NPF Metrics Instrumentation): Add first-pass PR validation rate, stage cycle timestamps, qualification verdict/reasons, human override rate, privacy-safe operatorHash, and PR metadata support.
    • Add PowerBIActivity schema definitions to workbook migration catalogs.

Reason for Change(s):

  • Complete the qualification-first workflow, static KQL validation and qualification override for XDR artifacts, strict V3.1 packaging checks, and NPF metrics instrumentation.

Version Updated:

  • N/A (tooling and validation infrastructure changes only)

Testing Completed:

  • Yes
  • pytest Tools/SentinelToXDRMigration/tests/test_release_hygiene.py Tools/SentinelToXDRMigration/tests/test_runtime_consent.py Tools/SentinelToXDRMigration/tests/test_target_config.py Tools/SentinelToXDRMigration/tests/test_qualification_orchestrator.py Tools/SentinelToXDRMigration/tests/test_packaging.py Tools/SentinelToXDRMigration/tests/test_workflow.py Tools/SentinelToXDRMigration/tests/test_authoring_kql_validator.py Tools/SentinelToXDRMigration/tests/test_pr_qualification_engine.py Tools/SentinelToXDRMigration/tests/test_pr_qualification_report.py Tools/SentinelToXDRMigration/tests/test_decision_manifest.py -q — Passed (122 passed)
  • dotnet test .script/tests/KqlvalidationsTests/Kqlvalidations.Tests.csproj --filter "FullyQualifiedName~Validate_XdrDetectionQueries_HaveValidKql" — Passed (92 passed, 0 failed)

Checked that the validations are passing and have addressed any issues that are present:

  • Yes

idoshabi07 and others added 2 commits October 28, 2026 15:07
…c override, and NPF metrics (Todos #0-Azure#6)

- Todo #0: isolate Azure CLI profile in runtime consent and mock credential constructors in pytest conftest
- Todo #1: add live CD + AR enablement, alert parity readiness, and --keep-enabled support
- Todo #2: enforce strict V3.1 packaging verification for XDR Detections and XDR Workbooks
- Todo #3: default workflow profile to qualification and fix Windows directory fsync in budget.py
- Todo #4: add static KQL validation for XDR Detections and XDR Workbooks in Python and KqlValidationTests.cs
- Todo Azure#5: project kqlRuntimeVerifiedRules SHA-256 hashes and downgrade semantic KQL errors on qualified rules
- Todo Azure#6: emit NPF metrics instrumentation across pr-qualification-result.json, workflow-state.json, decision-manifest.json, and npf-pr-labeler.yaml

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521
@idoshabi07
idoshabi07 force-pushed the toolkit-publication-qualification-followups branch from 9add21a to e96de75 Compare October 28, 2026 13:39
idoshabi07 and others added 5 commits October 29, 2026 11:55
…ingestion

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521
…rting

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521
…c PR body

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2f9e241a-6b82-49ec-ac18-e83c833f2521

var candidateEvidenceFiles = new[]
{
Path.Combine(solutionDir, "Evidence", "pr-qualification", "pr-qualification-result.json"),
var candidateEvidenceFiles = new[]
{
Path.Combine(solutionDir, "Evidence", "pr-qualification", "pr-qualification-result.json"),
Path.Combine(solutionDir, "Evidence", "qualification-evidence.json")
Comment on lines +574 to +577
catch
{
// Ignore malformed evidence JSON and continue
}
Comment on lines +598 to +601
catch
{
// Ignore path resolution errors
}
Comment on lines +643 to +696
foreach (var token in verifiedRulesTokens)
{
if (token is JArray arr)
{
foreach (var item in arr)
{
if (item is JValue val && val.Type == JTokenType.String)
{
var strVal = val.Value<string>()?.Trim();
if (!string.IsNullOrEmpty(strVal) &&
(strVal.Equals(normalizedId, StringComparison.OrdinalIgnoreCase)
|| strVal.Equals(strippedId, StringComparison.OrdinalIgnoreCase)
|| queryHashes.Contains(strVal)))
{
return true;
}
}
else if (item is JObject obj)
{
var status = (string)obj["status"] ?? (string)obj["result"] ?? "PASS";
if (!status.Equals("PASS", StringComparison.OrdinalIgnoreCase)
&& !status.Equals("passed", StringComparison.OrdinalIgnoreCase)
&& !status.Equals("true", StringComparison.OrdinalIgnoreCase))
{
continue;
}

var entryHash = (string)obj["querySha256"] ?? (string)obj["queryHash"] ?? (string)obj["sha256"];
if (!string.IsNullOrWhiteSpace(entryHash))
{
if (queryHashes.Contains(entryHash.Trim()))
{
return true;
}
continue;
}

var ruleIds = new[]
{
(string)obj["ruleId"],
(string)obj["id"],
(string)obj["originalRuleId"],
(string)obj["sourceRuleId"]
};
if (ruleIds.Any(r => !string.IsNullOrWhiteSpace(r) &&
(r.Trim().Equals(normalizedId, StringComparison.OrdinalIgnoreCase)
|| r.Trim().Equals(strippedId, StringComparison.OrdinalIgnoreCase))))
{
return true;
}
}
}
}
}
{
var basePath = Utils.GetTestDirectory(TestFolderDepthForSolutionParsers);
var tempSolutionDir = Path.Combine(basePath, "Solutions", $"_TempQualTest_{Guid.NewGuid():N}");
var xdrDetDir = Path.Combine(tempSolutionDir, "XDR Detections");
var basePath = Utils.GetTestDirectory(TestFolderDepthForSolutionParsers);
var tempSolutionDir = Path.Combine(basePath, "Solutions", $"_TempQualTest_{Guid.NewGuid():N}");
var xdrDetDir = Path.Combine(tempSolutionDir, "XDR Detections");
var evidenceDir = Path.Combine(tempSolutionDir, "Evidence", "pr-qualification");
try
{
var ruleId = "xdr-11111111-2222-3333-4444-555555555555";
var ruleFilePath = Path.Combine(xdrDetDir, "TempRule.yaml");
}
}
};
File.WriteAllText(Path.Combine(evidenceDir, "pr-qualification-result.json"), evidenceJson.ToString());
protected override List<string> GetDirectoryPaths()
{
var basePath = Utils.GetTestDirectory(TestFolderDepth);
var solutionsDir = Path.Combine(basePath, "Solutions");
@idoshabi07
idoshabi07 marked this pull request as ready for review October 29, 2026 15:01
@idoshabi07
idoshabi07 requested review from a team as code owners October 29, 2026 15:02
target = info.get("targetTable") or info.get("ahTable")
if isinstance(target, str) and target.strip():
known.add(target.strip())
except Exception:
existing_artifact.stat().st_mtime_ns,
hashlib.sha256(existing_artifact.read_bytes()).hexdigest(),
)
run_started_ns = time.time_ns()
try:
resolved_run = max(1, int(env_val))
break
except (TypeError, ValueError):
from pathlib import Path
import tempfile
from typing import Iterator
import unittest
if _PROCESS_GUARD_STACK is None:
stack = ExitStack()
stack.enter_context(isolated_auth_guard())
_PROCESS_GUARD_STACK = stack
return original_run(self, result=result)

unittest.TestCase.run = _guarded_run # type: ignore[method-assign]
_UNITTEST_GUARD_INSTALLED = True
@@ -1,11 +1,18 @@
from __future__ import annotations

import json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants