Skip to content

feat: add Sysmon EVTX parser and workspace - #72

Merged
adamgell merged 27 commits into
mainfrom
claude/sysmon-evtx-parser-Bzdpl
Apr 3, 2026
Merged

feat: add Sysmon EVTX parser and workspace#72
adamgell merged 27 commits into
mainfrom
claude/sysmon-evtx-parser-Bzdpl

Conversation

@adamgell

@adamgell adamgell commented Mar 31, 2026

Copy link
Copy Markdown
Owner
  • Fix requestId scoping in sysmon-store.ts (add currentRequestId, gate updateProgress)
  • Pass requestId to beginAnalysis in Toolbar.tsx
  • Sort events by timestamp_ms in commands/sysmon.rs
  • Use timestamp_ms for earliest/latest in build_summary (evtx_parser.rs)
  • Set SysmonConfig.found based on actual config data presence (evtx_parser.rs)
  • Remove configuration_xml from event.message (evtx_parser.rs)
  • Fix file discovery comment (evtx_parser.rs)
  • Use is_sysmon_evtx only (no path-based shortcut) in commands/sysmon.rs
  • Use theme severityPalette for severity colors in SysmonEventTable.tsx
  • Add aria-label to search input in SysmonEventTable.tsx
  • Make EventRow accessible (role, tabIndex, onKeyDown) in SysmonEventTable.tsx
  • Fix pre-existing clippy warnings (needless_question_mark, unnecessary if-let, manual-range-patterns)

Copilot AI review requested due to automatic review settings March 31, 2026 02:20

Copilot AI 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.

Pull request overview

Adds Sysmon EVTX analysis support to CMTrace Open by introducing a new Rust backend parser/IPC command and a dedicated frontend “Sysmon” workspace to view events, summary statistics, and inferred configuration.

Changes:

  • Backend: add Sysmon EVTX parsing (per-event field extraction), summary/config inference, and an analyze_sysmon_logs Tauri command with progress events + Rayon parallelism.
  • Frontend: add Sysmon workspace UI (events table w/ virtualization + filters, summary dashboard, config viewer) and a Zustand store + progress listener hook.
  • App wiring: expose the workspace across navigation (Toolbar open actions, AppShell routing, StatusBar, FileSidebar, ui-store workspace availability).

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/types/sysmon.ts Defines frontend Sysmon types (events, summary, config, analysis result).
src/stores/ui-store.ts Registers sysmon workspace and chrome labels.
src/stores/ui-store.test.ts Updates workspace availability tests to include sysmon.
src/stores/sysmon-store.ts Adds Zustand state/actions for Sysmon analysis + UI filters/selection.
src/lib/commands.ts Adds analyzeSysmonLogs() Tauri invoke wrapper.
src/hooks/use-sysmon-analysis-progress.ts Listens for sysmon-analysis-progress events and updates store.
src/components/sysmon/SysmonWorkspace.tsx Top-level Sysmon workspace view with tabs + loading/error/empty states.
src/components/sysmon/SysmonSummaryView.tsx Renders summary metrics, time range, type breakdown, source file list.
src/components/sysmon/SysmonEventTable.tsx Virtualized event list with type/severity/search filters + detail expansion.
src/components/sysmon/SysmonConfigView.tsx Displays inferred Sysmon config metadata, active types, and XML (if present).
src/components/layout/Toolbar.tsx Wires open actions to trigger Sysmon analysis and store updates.
src/components/layout/StatusBar.tsx Adds Sysmon-specific status text and counts.
src/components/layout/FileSidebar.tsx Routes sysmon to the sidebar component used for file sources.
src/components/layout/AppShell.tsx Mounts Sysmon workspace and progress listener hook.
src-tauri/src/sysmon/models.rs Adds backend Sysmon models serialized to the frontend.
src-tauri/src/sysmon/mod.rs Exposes Sysmon module.
src-tauri/src/sysmon/evtx_parser.rs Implements EVTX discovery, Sysmon parsing, summary building, config inference.
src-tauri/src/lib.rs Registers sysmon module and exposes the new command.
src-tauri/src/commands/sysmon.rs Adds analyze_sysmon_logs command with parallel parsing + progress events.
src-tauri/src/commands/mod.rs Exposes the new sysmon command module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +13 to +100
export interface SysmonAnalysisProgress {
requestId: string;
stage: string;
message: string;
completedFiles: number;
totalFiles: number;
}

interface SysmonState {
events: SysmonEvent[];
summary: SysmonSummary | null;
config: SysmonConfig | null;
sourcePath: string | null;
isAnalyzing: boolean;
analysisError: string | null;
progressMessage: string | null;

// Interaction state
selectedEventId: number | null;
activeTab: SysmonWorkspaceTab;
filterEventType: SysmonEventType | "All";
filterSeverity: SysmonSeverity | "All";
searchQuery: string;

// Actions
beginAnalysis: (path: string) => void;
setResults: (result: SysmonAnalysisResult) => void;
failAnalysis: (error: string) => void;
updateProgress: (progress: SysmonAnalysisProgress) => void;
selectEvent: (id: number | null) => void;
setActiveTab: (tab: SysmonWorkspaceTab) => void;
setFilterEventType: (type_: SysmonEventType | "All") => void;
setFilterSeverity: (severity: SysmonSeverity | "All") => void;
setSearchQuery: (query: string) => void;
clear: () => void;
}

export const useSysmonStore = create<SysmonState>((set) => ({
events: [],
summary: null,
config: null,
sourcePath: null,
isAnalyzing: false,
analysisError: null,
progressMessage: null,
selectedEventId: null,
activeTab: "events",
filterEventType: "All",
filterSeverity: "All",
searchQuery: "",

beginAnalysis: (path) =>
set({
events: [],
summary: null,
config: null,
sourcePath: path,
isAnalyzing: true,
analysisError: null,
progressMessage: "Starting Sysmon analysis...",
selectedEventId: null,
filterEventType: "All",
filterSeverity: "All",
searchQuery: "",
}),

setResults: (result) =>
set({
events: result.events,
summary: result.summary,
config: result.config,
sourcePath: result.sourcePath,
isAnalyzing: false,
analysisError: null,
progressMessage: null,
}),

failAnalysis: (error) =>
set({
isAnalyzing: false,
analysisError: error,
progressMessage: null,
}),

updateProgress: (progress) =>
set({
progressMessage: progress.message,
}),

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

Sysmon analysis progress updates aren’t scoped to the active request. updateProgress always overwrites progressMessage, so if the user starts a second Sysmon analysis before the first finishes, late progress events from the first request can update the UI for the new run. Consider storing the current requestId in the Sysmon store (set in beginAnalysis) and ignoring progress payloads with a different requestId (and/or when isAnalyzing is false).

Copilot uses AI. Check for mistakes.
Comment on lines +435 to +454
const analyzeSysmonWorkspaceSource = useCallback(
async (source: LogSource, trigger: string) => {
useUiStore.getState().ensureWorkspaceVisible("sysmon", trigger);
const sourcePath = getLogSourcePath(source);
const requestId = `sysmon-${Date.now()}`;
beginSysmonAnalysis(sourcePath);

try {
const result = await analyzeSysmonLogs(sourcePath, requestId);
startTransition(() => {
setSysmonResults(result);
});
} catch (error) {
console.error("[app-actions] failed to analyze Sysmon source", {
source,
trigger,
error,
});
failSysmonAnalysis(error instanceof Error ? error.message : String(error));
}

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

beginSysmonAnalysis is invoked without the requestId that’s sent to the backend. Without persisting that requestId in the Sysmon store, the progress listener can’t filter out stale sysmon-analysis-progress events from prior runs. Pass the generated requestId into beginAnalysis and use it to gate progress updates (similar to Intune’s requestId handling).

Copilot uses AI. Check for mistakes.
Comment thread src-tauri/src/commands/sysmon.rs Outdated
Comment on lines +175 to +179
// Sort by timestamp, then by record_id for stable ordering
all_events.sort_by(|a, b| {
a.timestamp
.cmp(&b.timestamp)
.then_with(|| a.record_id.cmp(&b.record_id))

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

Events are sorted by the raw timestamp string. Sysmon timestamps can have variable fractional-second precision (e.g. ...22.025Z vs ...22.025812200Z), and lexicographic ordering will misorder those (because Z compares after digits). Sort by timestamp_ms when available (falling back to timestamp/record_id only when needed) to ensure chronological ordering.

Suggested change
// Sort by timestamp, then by record_id for stable ordering
all_events.sort_by(|a, b| {
a.timestamp
.cmp(&b.timestamp)
.then_with(|| a.record_id.cmp(&b.record_id))
// Sort by timestamp_ms when available, falling back to timestamp and then record_id for stable ordering
all_events.sort_by(|a, b| {
match (a.timestamp_ms, b.timestamp_ms) {
(Some(ta), Some(tb)) => ta
.cmp(&tb)
.then_with(|| a.record_id.cmp(&b.record_id)),
_ => a.timestamp
.cmp(&b.timestamp)
.then_with(|| a.record_id.cmp(&b.record_id)),
}

Copilot uses AI. Check for mistakes.
Comment on lines +256 to +264
if !event.timestamp.is_empty() {
let ts = event.timestamp.as_str();
if earliest.is_none() || ts < earliest.unwrap() {
earliest = Some(ts);
}
if latest.is_none() || ts > latest.unwrap() {
latest = Some(ts);
}
}

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

build_summary computes earliest/latest timestamps using lexicographic string comparison on event.timestamp. As with the main sort, variable fractional precision can produce incorrect min/max results. Use timestamp_ms (or parsed numeric timestamps) for earliest/latest selection, with a safe fallback when parsing fails.

Copilot uses AI. Check for mistakes.
Comment on lines +365 to +373
SysmonConfig {
schema_version,
hash_algorithms,
found: !events.is_empty(),
last_config_change,
configuration_xml,
sysmon_version,
active_event_types: summary.event_type_counts.clone(),
}

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

SysmonConfig.found is set to !events.is_empty(), which makes the frontend treat configuration data as “found” whenever any Sysmon events exist—even if there were no ConfigChange events or other config metadata. This will prevent the “No Sysmon configuration data found…” state from ever showing for a normal dataset without config events. Set found based on actual presence of config-relevant data (e.g., at least one EventID 16 / schema/version/hash info).

Copilot uses AI. Check for mistakes.
Comment thread src-tauri/src/sysmon/evtx_parser.rs Outdated
Comment on lines +23 to +41
/// Discovers Sysmon .evtx files in a directory (recursive one level).
pub fn discover_sysmon_evtx_files(root: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();

// Direct .evtx files in root
collect_evtx_files(root, &mut files);

// Check common subdirectories
for subdir in &["evidence", "event-logs", "evidence/event-logs"] {
let dir = root.join(subdir);
if dir.is_dir() {
collect_evtx_files(&dir, &mut files);
}
}

// Deduplicate by canonical path
files.sort();
files.dedup();
files

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

The file discovery comments don’t match the implementation: it says “recursive one level” but only checks the root and a few hard-coded subdirs, and it says “Deduplicate by canonical path” but only does sort()/dedup() on raw PathBufs. Either adjust the comments to reflect the actual behavior or implement true one-level recursion + canonicalized deduping.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
const SEVERITY_COLORS: Record<SysmonSeverity, string> = {
Info: tokens.colorBrandForeground1,
Warning: tokens.colorPaletteMarigoldForeground1,
Error: tokens.colorPaletteRedForeground1,
};

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

Severity colors are hard-coded to Fluent tokens. The app’s theming system already provides a per-theme severityPalette (used in the log workspace) for consistent colors and high-contrast compatibility. Consider deriving Sysmon severity colors from the active theme palette instead of using fixed token values.

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +155
<input
type="text"
placeholder="Search events..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
style={{
fontSize: "12px",
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
border: `1px solid ${tokens.colorNeutralStroke1}`,
borderRadius: "3px",
padding: "2px 8px",
minWidth: "200px",
flex: 1,
maxWidth: "400px",
}}
/>

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

The search input has no associated label (placeholder text isn’t a reliable accessible name). Add an explicit <label> or aria-label/aria-labelledby so screen readers can identify the control.

Copilot uses AI. Check for mistakes.
Comment on lines +235 to +265
<div
onClick={onClick}
style={{
display: "grid",
gridTemplateColumns: "160px 140px 70px 1fr",
padding: "4px 12px",
height: `${ROW_HEIGHT}px`,
alignItems: "center",
gap: "8px",
cursor: "pointer",
fontSize: "12px",
fontFamily: LOG_MONOSPACE_FONT_FAMILY,
backgroundColor: isSelected
? tokens.colorNeutralBackground1Selected
: "transparent",
borderBottom: `1px solid ${tokens.colorNeutralStroke3}`,
}}
>
<span style={{ color: tokens.colorNeutralForeground3, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{formatTimestamp(event.timestamp)}
</span>
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{event.eventTypeDisplay}
</span>
<span style={{ color: SEVERITY_COLORS[event.severity] }}>
{event.severity}
</span>
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{event.message}
</span>
</div>

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

EventRow is a clickable <div> without keyboard interaction or semantic role, so it isn’t accessible to keyboard/screen-reader users. Consider using a <button> (styled to look like a row) or add role="button", tabIndex={0}, onKeyDown handling (Enter/Space), and an aria-selected state tied to isSelected.

Copilot uses AI. Check for mistakes.
Comment thread src-tauri/src/commands/sysmon.rs Outdated
Comment on lines +80 to +91
// Filter to only Sysmon EVTX files (if directory, check provider)
let sysmon_files: Vec<_> = if source_path.is_file() {
// Single file — trust the user
evtx_files
} else {
evtx_files
.into_iter()
.filter(|f| {
let name = f.file_name().unwrap_or_default().to_string_lossy().to_lowercase();
// Quick path-based check first
name.contains("sysmon") || evtx_parser::is_sysmon_evtx(f)
})

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

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

When analyzing a directory, the file filter accepts any .evtx whose filename contains sysmon without verifying the provider. If such a file doesn’t actually contain Sysmon events, parse_sysmon_evtx will yield an empty event set and the overall analysis may return 0 events without an error (which the frontend currently renders as the initial “Open a Sysmon .evtx…” empty state). Consider always validating via is_sysmon_evtx (or treating “0 events parsed” as an invalid input/error) so users get a clear failure instead of a silent empty result.

Suggested change
// Filter to only Sysmon EVTX files (if directory, check provider)
let sysmon_files: Vec<_> = if source_path.is_file() {
// Single file — trust the user
evtx_files
} else {
evtx_files
.into_iter()
.filter(|f| {
let name = f.file_name().unwrap_or_default().to_string_lossy().to_lowercase();
// Quick path-based check first
name.contains("sysmon") || evtx_parser::is_sysmon_evtx(f)
})
// Filter to only Sysmon EVTX files (if directory, validate provider)
let sysmon_files: Vec<_> = if source_path.is_file() {
// Single file — trust the user
evtx_files
} else {
evtx_files
.into_iter()
.filter(|f| evtx_parser::is_sysmon_evtx(f))

Copilot uses AI. Check for mistakes.
@adamgell

Copy link
Copy Markdown
Owner Author

@copilot apply changes based on the comments in this thread

Copilot AI commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments in this thread

Applied all 11 review feedback items in commit 6e47cb5:

  • requestId scoping: Added currentRequestId to the Sysmon store; beginAnalysis now accepts (path, requestId); updateProgress ignores progress events whose requestId doesn't match the active request or when analysis is not running.
  • Toolbar.tsx: requestId is now passed to beginSysmonAnalysis(sourcePath, requestId).
  • Timestamp sort: Events are sorted by timestamp_ms (numeric) with fallback to string timestamp + record_id.
  • build_summary min/max: Uses timestamp_ms for earliest/latest selection; falls back to string comparison only when timestamp_ms is unavailable.
  • SysmonConfig.found: Now set to last_config_change.is_some() || hash_algorithms.is_some() || sysmon_version.is_some() instead of !events.is_empty().
  • configuration_xml: Removed population from event.message; replaced with an explanatory comment.
  • File discovery comment: Updated to accurately describe actual behavior.
  • Directory validation: Uses is_sysmon_evtx exclusively (removed path-name shortcut).
  • Severity colors: Derived from the active theme's severityPalette via getThemeById(themeId).
  • Search input: Added aria-label="Search events".
  • EventRow accessibility: Added role="button", tabIndex={0}, onKeyDown (Enter/Space), and aria-selected.

@adamgell

Copy link
Copy Markdown
Owner Author

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

adamgell added a commit that referenced this pull request Mar 31, 2026
- Add "Sysmon Analysis" case to StatusBar activeViewLabel (issue 7)
- Replace LogSidebar with dedicated SysmonSidebar for sysmon workspace (issue 8)
- Fix sort comparator to handle (Some, None) and (None, Some) with nulls-last (issue 9)
- Fix build_summary to track string-only events even after numeric timestamps seen (issue 10)
- Add integration tests for public sysmon API in src-tauri/tests/ (issue 11)
- Change sysmon platform from "all" to ["windows"] in WORKSPACE_PLATFORM_MAP (issue 12)
- Replace hardcoded ROW_HEIGHT/fontSize with getLogListMetrics for accessibility (issue 14)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adamgell added a commit that referenced this pull request Apr 2, 2026
- Add "Sysmon Analysis" case to StatusBar activeViewLabel (issue 7)
- Replace LogSidebar with dedicated SysmonSidebar for sysmon workspace (issue 8)
- Fix sort comparator to handle (Some, None) and (None, Some) with nulls-last (issue 9)
- Fix build_summary to track string-only events even after numeric timestamps seen (issue 10)
- Add integration tests for public sysmon API in src-tauri/tests/ (issue 11)
- Change sysmon platform from "all" to ["windows"] in WORKSPACE_PLATFORM_MAP (issue 12)
- Replace hardcoded ROW_HEIGHT/fontSize with getLogListMetrics for accessibility (issue 14)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamgell
adamgell force-pushed the claude/sysmon-evtx-parser-Bzdpl branch from 7e0db09 to 66a0d25 Compare April 2, 2026 23:34
claude and others added 19 commits April 2, 2026 20:05
Add full Sysmon event log analysis capability using the existing evtx crate.
Includes a Rust backend module for parsing Sysmon EVTX files with event-type-specific
field extraction (29 event types), configuration inference, and summary statistics.
Frontend adds a new "Sysmon" workspace with virtual-scrolled event table, summary
dashboard, and configuration viewer.

Backend:
- src-tauri/src/sysmon/ - models, evtx_parser with per-event message building
- src-tauri/src/commands/sysmon.rs - analyze_sysmon_logs IPC command with
  parallel file parsing via Rayon and progress events

Frontend:
- Sysmon workspace with Events/Summary/Config tabs
- Virtual-scrolled event table with type/severity/search filtering
- Sysmon config viewer showing hash algorithms, active event types
- Zustand store, TypeScript types, progress listener hook
- Workspace navigation wired into Toolbar, AppShell, StatusBar, FileSidebar

https://claude.ai/code/session_01A9vm5TTW1UXxNc9VzdK9LQ
Agent-Logs-Url: https://github.com/adamgell/cmtraceopen/sessions/0584db31-ce58-43b2-bfcf-23622a9057d6

Co-authored-by: adamgell <27519+adamgell@users.noreply.github.com>
- Add "Sysmon Analysis" case to StatusBar activeViewLabel (issue 7)
- Replace LogSidebar with dedicated SysmonSidebar for sysmon workspace (issue 8)
- Fix sort comparator to handle (Some, None) and (None, Some) with nulls-last (issue 9)
- Fix build_summary to track string-only events even after numeric timestamps seen (issue 10)
- Add integration tests for public sysmon API in src-tauri/tests/ (issue 11)
- Change sysmon platform from "all" to ["windows"] in WORKSPACE_PLATFORM_MAP (issue 12)
- Replace hardcoded ROW_HEIGHT/fontSize with getLogListMetrics for accessibility (issue 14)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sysmon workspace was changed to ["windows"] in WORKSPACE_PLATFORM_MAP,
so tests for macOS and Linux should expect sysmon to NOT be available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Wire sysmonIsAnalyzing into isSourceCommandBusy so Sysmon analysis
  blocks other source commands while running
- Add sysmon case to canRefresh and hasActiveSource using sysmonSourcePath
- Add sysmon case to refreshActiveSource to re-run analysis on the same path
- Add a Refresh button in the SysmonWorkspace tab bar header, wired to
  refreshActiveSource (also works via the existing sidebar footer button)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add TimeBucket, RankedItem, SecuritySummary, and SysmonDashboardData structs
to models.rs. Add dashboard field to SysmonAnalysisResult.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add build_dashboard_data() that computes timeline buckets (minute/hourly/daily),
top-N lists for processes, network destinations, ports, DNS, files, and registry,
plus security event counts by severity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Call build_dashboard_data() after extract_config() and include the
resulting SysmonDashboardData in SysmonAnalysisResult.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
adamgell and others added 8 commits April 2, 2026 20:06
Downsample time buckets to max 100 bars, use calculated bar width
instead of auto, and constrain container with overflow:hidden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DonutChart: 280px → 200px, innerRadius 55 → 40
- VerticalBarChart: 220px → 180px
- Grid: alignItems: start so cards don't stretch to fill row height

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminates empty space by putting Event Type Distribution and Security
Alerts in a stacked left column with Top Processes filling the right.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add design and implementation plan for a Sysmon dashboard and wire up initial backend/frontend support. This change introduces two new docs (plan + spec) under docs/superpowers, updates CLAUDE.md to reference the Sysmon workspace and adds an agent-directives block, and enables related plugins in .claude/settings.local.json. On the code side, EVTX parsing and related Intune/eventlog code were updated to support dashboard aggregations (evtx_parser.rs, eventlog_win32.rs, known_sources.rs), and frontend integration helpers were adjusted (Toolbar.tsx, src/lib/commands.ts). The commit groups planning/spec content with the required code changes to begin implementing precomputed dashboard data and UI wiring.
…ibility improvements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamgell
adamgell force-pushed the claude/sysmon-evtx-parser-Bzdpl branch from 3dc2981 to 05ebc31 Compare April 3, 2026 00:09
@adamgell
adamgell merged commit e2ca25a into main Apr 3, 2026
4 of 5 checks passed
adamgell added a commit that referenced this pull request Apr 5, 2026
Resolve 4 outstanding Copilot review issues plus changelog updates:

- DiffConfigDialog: filter to log-only tabs, validate selections exist
- FileSidebar: filter directories and uncached files before merge
- session.ts: replace unsafe cast with field-by-field validation and defaults
- session-restore: load files individually for per-tab restore, restore
  active tab index and scroll positions

Update changelog with all unreleased features, PR references (#72, #78,
#79, #81, #82), and new Security section for CI permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adamgell added a commit that referenced this pull request Apr 6, 2026
* feat(merge): add showMergeTabsDialog state to ui-store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add pure merge logic — sorting, colors, correlation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add MergedTabState, merge/correlation actions to log store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): create MergeLegendBar with file toggles and correlation controls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): create MergeTabsDialog component

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add Merge Tabs button to toolbar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): wire MergeTabsDialog into AppShell

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): show merged tab indicator in tab strip

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add file color borders, correlation highlights, and legend bar to log view

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add Merge into Timeline button to folder sidebar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(merge): add correlated entries section to InfoPane

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add unified timeline spec and implementation plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add session save/restore and log diff specs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add recentSessions state to ui-store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add compute_file_hash Rust command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add normalization, pattern key, and classification logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add session types, save, and restore logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add DiffState and diff actions to log store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(session): add Save/Open Session menu items and handlers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): add DiffConfigDialog, toolbar button, and ui-store state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(diff): create DiffView with side-by-side and unified modes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: wire DiffConfigDialog into AppShell, render DiffView, update changelog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address code review — scroll sync, session restore, validation, performance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot PR review — accessibility, UTF-8, GUID casing, cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add Graph API GUID resolution (Windows)

Introduce end-to-end Microsoft Graph integration to resolve Intune app GUIDs. Backend: new src-tauri/src/graph_api.rs implements WAM-based Windows auth, token caching, batch/single Graph requests, paginated app fetches and an in-memory GUID cache; adds fetch_all_apps and resolve_guids utilities. Frontend: new GraphApiTab settings UI to enable/sign-in/pre-populate cache, a startup hook to auto-connect and populate cache, status bar indicator for Graph status, and enhancements to the GUID registry dialog (tabs, filtering, publisher/type columns). Add lib/graph-registry.ts to convert GraphAppInfo to GuidRegistry entries and update types (GuidCategory, publisher). Also improve event_tracker to extract PolicyId from JSON payloads. Wire startup import in main.tsx and add ui-store state/handlers for graphApiEnabled and graphApiStatus.

* feat: add Microsoft Graph API integration for GUID resolution

Add opt-in Graph API support to resolve Intune app GUIDs to display
names using the device's existing Entra ID session via WAM (Web Account
Manager). No app registration required — uses the Microsoft Graph
PowerShell public client ID.

- WAM authentication with HWND interop for Win32 desktop apps
- Graph API client with batch resolution ($batch endpoint, 20 per request)
- Pre-populate cache button fetches all tenant apps in one call
- GraphApi source variant (highest confidence) in GuidRegistry
- Settings tab with opt-in toggle (off by default), consent warnings,
  and connection status display
- Automatic enrichment during Intune log analysis when enabled

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts with Graph API integration, fix CI

- Fix analyze_intune_logs to use app.state() instead of cfg-gated parameter
  (Tauri generate_handler! can't handle #[cfg] on parameters)
- Gate Manager import to windows-only in both lib.rs and intune.rs
- Resolve merge conflicts in ui-store.ts (recentSessions + graphApi state)
- Resolve merge conflicts in GraphApiTab.tsx (keep buildGraphRegistryEntries)
- Fix GuidRegistryDialog aria-label to use entry.guid

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR #82 review — ID collisions, persist sessions, correlation refresh, diff close

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: force clean build to resolve stale cache

* fix: add missing script_body and parent_app_guid to RFC3339 test

The sysmon merge brought a new test (build_timeline_sorts_rfc3339_timestamps)
that was missing the two fields added to IntuneEvent in the AppWorkload PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: add explicit permissions to CI workflow

Restrict GITHUB_TOKEN to contents:read across all jobs to satisfy
the principle of least privilege. Resolves CodeQL alerts #1#3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gate sysmon module behind feature flag, fix clippy

- Add sysmon feature to Cargo.toml, include in full feature set
- Gate pub mod sysmon and analyze_sysmon_logs command with #[cfg(feature = "sysmon")]
- Gate sysmon commands mod with #[cfg(feature = "sysmon")]
- Add required-features = ["sysmon"] to sysmon_parser test
- Fix clippy needless_range_loop in event_tracker.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts in graph_api.rs

Unresolved conflict markers from d065202 merge caused unclosed
delimiter errors on Windows CI builds. Kept the refactored
fetch_paginated helper that supports multiple Intune endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve merge conflicts from upstream session/diff features

Merge Graph API integration (graphApiEnabled, graphApiStatus) with
upstream session save/restore (recentSessions) and diff features.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: implement live Windows Event Log queries

Implement EvtOpenChannelEnum, EvtQuery, EvtNext, EvtRender, and
EvtFormatMessage via Win32 API to query live event log channels.
Auto-loads Application, System, Security, and Setup on "This Computer".

- Raw FFI for channel enumeration (fixes NULL handle issue)
- Buffer retry for EvtRender with both Win32 and HRESULT error codes
- Rendered messages via EvtFormatMessage with publisher metadata cache
- XML string parsing for EventID, Level, Provider, TimeCreated, EventData
- Progressive channel loading with per-channel error handling
- Channel picker: Event Viewer-style tree (Windows Logs / App & Services)
- Resizable channel sidebar with drag handle
- Resizable detail pane with drag handle
- Arrow key navigation in timeline (Up/Down/Home/End)
- Progress bar during channel loading
- Status bar shows "Event Log" with channel/event counts
- Load button for querying additional checked channels
- Error messages surfaced to frontend via errorMessages field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: live event log queries, Graph API scripts, parallel loading

Live Event Log:
- Implement Win32 EvtQuery/EvtRender/EvtFormatMessage for live channel queries
- Auto-load Application, System, Security, Setup in parallel
- Event Viewer-style nested tree sidebar (split on - and /)
- Resizable sidebar and detail pane with drag handles
- Arrow key navigation in timeline
- Progressive loading with spinner and elapsed time in status bar
- Refresh button to reload channels
- No event cap — loads all events on disk
- DevTools auto-open in debug builds

Graph API enhancements:
- Fetch remediation scripts, platform scripts, shell scripts
- GUID Registry tabbed view (All/Apps/Scripts/Remediations)
- Category and publisher columns
- Auto-connect on startup with status bar indicator
- PolicyId extraction for HealthScripts events

Fixes:
- HRESULT error code handling (low 16-bit extraction)
- EvtRender buffer retry for large events
- Status bar shows correct workspace labels
- Hide FileSidebar in Event Log workspace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: gate devtools and progress events for cross-platform CI

- Gate open_devtools behind #[cfg(all(debug_assertions, desktop))]
- Gate EvtxQueryProgress, Serialize, Emitter behind #[cfg(target_os = "windows")]
- Fixes cargo check on Ubuntu CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address remaining Copilot PR #82 review comments, update changelog

Resolve 4 outstanding Copilot review issues plus changelog updates:

- DiffConfigDialog: filter to log-only tabs, validate selections exist
- FileSidebar: filter directories and uncached files before merge
- session.ts: replace unsafe cast with field-by-field validation and defaults
- session-restore: load files individually for per-tab restore, restore
  active tab index and scroll positions

Update changelog with all unreleased features, PR references (#72, #78,
#79, #81, #82), and new Security section for CI permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add workspace registry design spec

Defines a pluggable workspace registry to replace 25+ scattered if/else
chains across 8 frontend files with centralized, type-safe definitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add workspace registry implementation plan (Phase 1-2)

9 tasks covering foundation types, registry, and sysmon migration
as the template workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(workspaces): add WorkspaceDefinition types

* feat(workspaces): add central workspace registry

* refactor(sysmon): move sysmon types to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move sysmon store to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move sysmon components to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): move analysis progress hook to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sysmon): extract SysmonSidebar to src/workspaces/sysmon/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(workspaces): register sysmon as first workspace definition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add workspace registry Phase 3 implementation plan

6 tasks: shim definitions, AppShell/FileSidebar/ui-store/Toolbar refactors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(workspaces): add shim definitions for all workspaces

Creates shim WorkspaceDefinition objects for the 7 remaining workspaces
(log, intune, new-intune, dsregcmd, macos-diag, deployment, event-log),
exports LogSidebar/IntuneSidebar/DsregcmdSidebar from FileSidebar.tsx,
and registers all 8 workspaces in the workspace registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(appshell): use workspace registry for component routing

Replace the 8-way if/else chain in renderWorkspace() with a registry
lookup + Suspense for all non-log workspaces. Log workspace inline
rendering (DiffView, RegistryViewer, folder overlay, InfoPane) is
preserved as a special case. TabStrip and FindBar conditionals now
consult workspace capabilities flags instead of hard-coding activeView.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(sidebar): use workspace registry for sidebar routing

Replace the ternary chain (isIntuneWorkspace / activeView === 'sysmon'
checks) with a registry lookup via getWorkspace(activeView).sidebar, and
drive the footer bar from capabilities.footerBar. Remove now-unused
SysmonSidebar direct import and isIntuneWorkspace import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(ui-store): delegate platform gating to workspace registry

Remove WORKSPACE_PLATFORM_MAP constant and inline filter logic from
getAvailableWorkspaces(). Delegate to getRegistryWorkspaces() from
the workspace registry instead, mapping WorkspaceDefinition[] back
to WorkspaceId[] to preserve the existing public API surface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(toolbar): use workspace registry for labels and file filters

Replace WORKSPACE_LABELS, getOpenFileDialogFilters(), and
getOpenActionLabels() with direct lookups from getWorkspace() in the
workspace registry, eliminating ~75 lines of duplicated metadata.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(dsregcmd): migrate workspace to src/workspaces/dsregcmd/

Move all dsregcmd-specific files into src/workspaces/dsregcmd/ to make
the workspace fully self-contained, following the same pattern as sysmon.

- git mv types/dsregcmd.ts → workspaces/dsregcmd/types.ts
- git mv stores/dsregcmd-store.ts → workspaces/dsregcmd/dsregcmd-store.ts
- git mv components/dsregcmd/* → workspaces/dsregcmd/ (7 files)
- Extract DsregcmdSidebar from FileSidebar.tsx into workspaces/dsregcmd/DsregcmdSidebar.tsx
- Update index.ts shim to use local ./ imports
- Update all external importers (Toolbar, StatusBar, EvidenceBundleDialog,
  commands.ts, dsregcmd-source.ts, dsregcmd-store.test.ts, event-log/index.ts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(event-log): migrate workspace to src/workspaces/event-log/

Move types, store, and all 7 components out of src/types/, src/stores/,
and src/components/event-log-workspace/ into the unified workspace directory.
Update all internal imports to use relative paths within the workspace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(macos-diag): migrate workspace to src/workspaces/macos-diag/

Move types, store, and all 9 components from their scattered locations
(src/types/, src/stores/, src/components/macos-diag/) into the unified
workspace directory src/workspaces/macos-diag/. Update all internal
and external import paths accordingly. TypeScript passes clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(deployment): migrate workspace to src/workspaces/deployment/

Moves deployment-store.ts and all 3 component files (DeploymentWorkspace,
DeploymentErrorCard, DeploymentSuccessTable) from their legacy locations into
src/workspaces/deployment/. Updates all import paths in the moved files,
the workspace index shim, StatusBar.tsx, and both dynamic imports in Toolbar.tsx.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(intune): migrate workspace to src/workspaces/intune/

Moves all 19 intune components, the intune store (1,095 lines), types,
hook, and test file from their scattered locations into the consolidated
src/workspaces/intune/ directory. Extracts IntuneSidebar from
FileSidebar.tsx into its own file with helpers inlined. Updates all
importers across layout, dialogs, hooks, and lib modules. Both the
intune and new-intune workspace shims now reference local workspace paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove empty component directories after workspace migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: add .superpowers/ to gitignore, remove tracked artifacts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract SourceSummaryCard to shared component, consolidate getBaseName

- Create src/components/common/sidebar-primitives.tsx with SourceSummaryCard,
  SourceStatusNotice, SectionHeader, EmptyState, and SidebarActionButton
- Remove inline copies of all five components from FileSidebar, IntuneSidebar,
  DsregcmdSidebar, and SysmonSidebar; import from shared module instead
- Widen canonical getBaseName signature to string | null | undefined
- Remove exported getBaseName from log-store; import from file-paths instead
- Update all callers in log-source, dsregcmd-source, EvidenceBundleDialog,
  StatusBar, IntuneSidebar, SysmonSidebar, and FileSidebar to use file-paths
- Also removes local getDirectoryName copy from EvidenceBundleDialog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(ui-store): replace getUiChromeStatus if/else chain with registry lookup

Add statusLabel to WorkspaceDefinition; set overrides on log, intune, and
new-intune workspaces. getUiChromeStatus now resolves labels via getWorkspace()
and branches only on capabilities.detailsPane, eliminating the 8-way if/else.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(toolbar): wire onOpenSource handlers and capability-based commandState

Move workspace-specific analysis logic (intune, dsregcmd, sysmon, deployment)
from Toolbar.tsx into each workspace's onOpenSource in its definition. Replace
the openSourceForWorkspace if/else chain with a registry lookup. Add knownSources
and tailing to WorkspaceCapabilities and use them in commandState instead of
hardcoded workspace ID checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update evtx-store import path after workspace migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address Copilot PR #84 review comments

- event-log: remove incorrect DsregcmdSidebar, fix file filters to EVTX
- deployment: handle file sources by analyzing parent directory
- types: move IntuneTimestampBounds to shared types to break circular dep

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamgell
adamgell deleted the claude/sysmon-evtx-parser-Bzdpl branch July 30, 2026 00:23
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.

4 participants