Skip to content

feat(intune): model platform-script execution evidence - #386

Merged
adamgell merged 6 commits into
mainfrom
claude/359-intune-windows-scripts
Jul 31, 2026
Merged

feat(intune): model platform-script execution evidence#386
adamgell merged 6 commits into
mainfrom
claude/359-intune-windows-scripts

Conversation

@adamgell

@adamgell adamgell commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Refs #359. Deliberately not Closes — see "What is not done" at the bottom.

What this adds

cmtraceopen_parser::intune::apps::windows::scripts — a semantic analyzer over supplied IME evidence for the Intune platform-script lifecycle. It separates policy receipt, scheduling, process launch, output, exit status, retry behaviour, and service reporting, rather than treating a severity keyword or an exit code as the diagnosis.

Module Responsibility
sources.rs Artifact classification from file name and the CCM components actually present
rules.rs Per-record classification; decides nothing
reducer.rs Keyed transaction reduction
redaction.rs Deterministic privacy projection
models.rs Public camelCase-serialized contract

Two keying decisions worth reviewing

Execution blocks. Within one AgentExecutor artifact, records inherit the key of their own execution block — a contiguous run on one CCM thread beginning at a launch record — because a block is written by a single execution. Blocks never span artifacts, so an execution split across a rotation boundary leaves a visible unkeyed tail rather than a guessed join (fixture rotation-boundary).

Partial-key reconciliation. The primary IME log names a policy but almost never names the run; AgentExecutor names both. A policy-only record may adopt a run id when that policy has exactly one run in the supplied evidence. With two or more runs it keeps its partial key and stays ambiguous. Without this the reporting half of the lifecycle could never join its own execution.

Neither rule merges on timestamp, display name, or a shared AgentExecutor component.

Public API change

ImeLine gains two fields, both already parsed on every CCM record but previously kept private on ParsedImeRecord (the duplicate copies are removed):

  • thread: Option<u32> — lets the reducer key interleaved executions instead of guessing from adjacency.
  • timezone_offset: Option<i32> — lets a consumer tell a source-stated offset from the parser's local-timezone fallback.

Additive on a published struct; no in-repo consumer constructs ImeLine outside tests.

Defects found and fixed by review

The second commit fixes six defects that review surfaced, each with a regression test:

  1. Unconfirmed artifacts could mint a transaction. Identifiers were extracted before the source kind was consulted, so a file merely named AgentExecutor.log could create a transaction from any line containing a GUID — contradicting sources.rs's own stated guarantee.
  2. normalized_utc was not what its doc comment claimed. ime_parser fills timestamp_utc from the parsing machine's local offset when a record carries none, which IME records usually do not. The old golden file had this machine's timezone baked into it and would have failed for any developer in another timezone.
  3. Confidence was demoted bundle-wide. One unrecognised record anywhere dropped every transaction to low confidence, including fully evidenced ones for unrelated policies.
  4. Multi-line records skipped command-line redaction entirely — the regex anchored on $, which does not match at a line break.
  5. Redaction covered only -Command/-EncodedCommand-Password, -ApiKey, -ClientSecret exported verbatim.
  6. Ordering ignored time even when time was trustworthy, which decides which attempt a retry sequence reports.

Fixture matrix

The 15 scenarios required by the issue are under tests/fixtures/intune/windows/scripts/, each with manifest.json, its evidence files, and a written-out expected.json so a reviewer can see what is being asserted. Two more were added for the review fixes (multi-policy-partial-unknown-version, explicit-timezone-offset). success-device-context additionally carries expected-full.json, a golden of the complete redacted export (regenerate with UPDATE_SCRIPT_GOLDEN=1).

Verification

Run from the repository root:

  • cargo test --locked -p cmtraceopen-parser — 397 unit (baseline 355), 222 esp integration, 21 intune_windows_scripts integration, 1 doc test. 0 failed.
  • cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings — clean.
  • cargo test --locked --manifest-path src-tauri/Cargo.toml --all-features — 427 + 180 + 82 + others, 0 failed.
  • cargo clippy --locked -p cmtrace-open --all-targets --all-features -- -D warnings — clean.
  • npx tsc --noEmit — exit 0 (unchanged; no frontend surface in this PR).

What is not done

The event_tracker.rs consolidation the issue asks for is not in this PR. #359 says to "migrate or wrap the relevant ImeSourceKind::AgentExecutor behavior from intune/event_tracker.rs so there is one ruleset". That has not happened: event_tracker.rs still has its own source classification and its own policy-id, exit-code, and timeout regexes, and those regexes are not equivalent to the new ones (its exit-code pattern is decimal-only; the new one also reads hex). Two rulesets exist in the crate today. This PR uses Refs rather than Closes so #359 stays open for that work.

Also out of scope here: no native known-source or frontend changes (the analyzer is pure and consumes artifacts the caller already read and decoded), and no remediation pairing — HealthScripts is recognised as a distinct source and deliberately carries no key, pending #360.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Windows Intune platform-script analysis covering execution states, retries, timeouts, exit codes, context, bitness, confidence, and evidence coverage.
    • Added correlation across log artifacts, including missing or unclassified evidence detection.
    • Added deterministic redaction for sensitive paths, account identifiers, commands, and credentials.
    • Added timestamp handling with timezone offsets and expanded parsed log metadata.
  • Tests

    • Added comprehensive scenarios for successful, failed, partial, retried, timed-out, and privacy-sensitive script executions.

adamgell and others added 2 commits July 31, 2026 00:55
Before: `ImeLine` exposed line number, timestamp, message, and component.
The `thread=` attribute was parsed out of every CCM record but kept private
on `ParsedImeRecord`, so it reached `LogEntry` and stopped there. Any
consumer of `parse_ime_content` that needed to group a sequence of records
belonging to one operation had only adjacency to go on.

This moves the field onto `ImeLine` and drops the now-duplicated copy from
`ParsedImeRecord`, which reads it back through `entry.line.thread`.

This is the right seam because thread is record-level evidence, not a
derived display value: it belongs on the record projection alongside
component, and keeping one owner avoids the same value living in two
structs. The upcoming `intune::apps::windows::scripts` reducer needs it to
key interleaved AgentExecutor executions without merging on timestamp
proximity, which the issue explicitly forbids.

This is an additive public field on a published struct. No in-repo consumer
constructs `ImeLine` outside tests; those literals are updated here.

Verified with `cargo test --locked -p cmtraceopen-parser`: 355 unit and 222
integration tests pass, identical to the pre-change baseline.

Refs #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Before: Intune platform-script evidence had no owner. `intune/event_tracker.rs`
recognised an `AgentExecutor` source kind and emitted loose events from it, but
there was no lifecycle: no way to say whether a policy was received, whether it
was launched, whether it exited, or whether the result ever reached the
service. A nonzero exit code was just a severity keyword in a line.

This adds `cmtraceopen_parser::intune::apps::windows::scripts`, a semantic
analyzer over supplied IME evidence:

- `sources.rs` classifies each artifact from its name *and* the CCM components
  actually inside it, so a file called `AgentExecutor.log` full of unrelated
  records stays `Unknown` and shows up as coverage rather than as evidence;
- `rules.rs` classifies one record at a time and decides nothing;
- `reducer.rs` groups records into transactions keyed on policy id, run id,
  and execution context, and reduces each to a state, a last confirmed phase,
  a typed exit token, an attempt count, a confidence, and the smallest next
  artifact worth asking for;
- `redaction.rs` masks UPNs, user profile segments, and command lines with a
  stable FNV-1a token, leaving the policy/run GUIDs a reader needs to follow
  the correlation.

This is the right seam because platform scripts, remediations, and Win32
installers reach terminal states for different reasons and must not share one
state machine. `HealthScripts` records are recognised as a distinct source and
never classified into a platform-script signal; issue #360 owns that lifecycle.

Two keying decisions are worth review. Within one AgentExecutor artifact,
records inherit the key of their own execution block -- a contiguous run on one
CCM thread starting at a launch record -- because that block is written by a
single execution. Blocks never span artifacts, so an execution split across a
rotation boundary leaves a visible unkeyed tail instead of a guessed join.
Separately, a record naming only a policy may adopt a run id when that policy
has exactly one run in the supplied evidence; with two or more it keeps its
partial key. Neither rule ever merges on timestamp, display name, or a shared
`AgentExecutor` component.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 392 unit (was 355), 222 esp
  integration, 18 new `intune_windows_scripts` integration, 1 doc test; 0
  failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

The 15 scenarios required by the issue are present under
`tests/fixtures/intune/windows/scripts/`, each with a manifest, its evidence,
and a written-out `expected.json`. `success-device-context` additionally
carries `expected-full.json`, a golden of the complete redacted export that
pins the serialized camelCase shape.

Closes #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 05:10
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@adamgell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1eed75da-5fd9-4dff-8a35-20a4bc004c1e

📥 Commits

Reviewing files that changed from the base of the PR and between 00512a1 and f09632c.

⛔ Files ignored due to path filters (3)
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/later-completion-without-readable-code/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/shared-ime-log-other-workload/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/shared-ime-log-other-workload/IntuneManagementExtension.log is excluded by !**/*.log
📒 Files selected for processing (7)
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/later-completion-without-readable-code/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/later-completion-without-readable-code/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/shared-ime-log-other-workload/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/shared-ime-log-other-workload/manifest.json
  • crates/cmtraceopen-parser/tests/intune_windows_scripts.rs
📝 Walkthrough

Walkthrough

Adds a public Windows Intune platform-script analyzer. It classifies supplied artifacts and records, reduces lifecycle evidence into transactions, redacts sensitive data, preserves coverage details, and validates behavior with fixture-driven tests.

Changes

Windows Intune platform-script analysis

Layer / File(s) Summary
Public contracts and IME line data
crates/cmtraceopen-parser/src/intune/apps/..., crates/cmtraceopen-parser/src/intune/ime_parser.rs, crates/cmtraceopen-parser/src/intune/*_parser.rs
Adds serialized script-analysis models, public module exports, and ImeLine fields for CCM thread and timezone offset data.
Artifact and record classification
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs, rules.rs
Classifies confirmed Windows script artifacts and extracts identifiers, execution metadata, lifecycle signals, and coverage indicators.
Evidence correlation and transaction reduction
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs
Correlates AgentExecutor blocks and partial identities, orders records, reduces lifecycle signals into transactions, and reports evidence coverage.
Sensitive evidence redaction
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs
Adds deterministic redaction for sensitive paths and observation messages while preserving public values and analysis structure.
Scenario and serialization validation
crates/cmtraceopen-parser/tests/intune_windows_scripts.rs, crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/*
Adds fixture-driven coverage for execution outcomes, retries, timestamps, grouping, missing evidence, serialization, and redacted exports.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

  • #359 — Directly implements the Windows platform-script execution evidence analyzer.
  • #356 — Implements the Windows platform-script child item under the Intune analyzer epic.
  • #361 — Adds a separate macOS script-analysis module with similar lifecycle and evidence concepts.
  • #360 — Adds a related Windows analyzer under the same hierarchy for remediation lifecycle evidence.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: modeling Intune platform-script execution evidence.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

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 a new pure semantic analyzer under cmtraceopen_parser::intune::apps::windows::scripts to model Intune Windows platform-script lifecycle evidence (keying, reduction, coverage, and deterministic redaction), plus a focused fixture-driven integration test matrix. Also exposes CCM thread= on ImeLine to support correlation without timestamp-based joins.

Changes:

  • Introduces intune::apps::windows::scripts analyzer (models, source classification, per-record rules, keyed reduction, redaction/export projection).
  • Adds intune_windows_scripts integration test harness with 15 scenario fixtures + one full redacted golden export.
  • Makes ImeLine.thread: Option<u32> public and updates related tests to populate it.

Reviewed changes

Copilot reviewed 46 out of 67 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/cmtraceopen-parser/src/intune/mod.rs Exposes new intune::apps module.
crates/cmtraceopen-parser/src/intune/ime_parser.rs Adds ImeLine.thread and plumbs parsed thread through.
crates/cmtraceopen-parser/src/intune/policy_parser.rs Updates tests for new ImeLine.thread field.
crates/cmtraceopen-parser/src/intune/guid_registry.rs Updates tests for new ImeLine.thread field.
crates/cmtraceopen-parser/src/intune/event_tracker.rs Updates tests for new ImeLine.thread field.
crates/cmtraceopen-parser/src/intune/download_stats.rs Updates tests for new ImeLine.thread field.
crates/cmtraceopen-parser/src/intune/apps/mod.rs Adds Intune workload analyzer module root.
crates/cmtraceopen-parser/src/intune/apps/windows/mod.rs Adds Windows analyzer namespace.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/mod.rs Public entrypoint + re-exports for scripts analyzer.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/models.rs Public serialized contract types for analysis output.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs Artifact source-kind + rotation classification logic.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs Record-level signal/key extraction rules and regexes.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs Transaction reduction + key reconciliation + coverage.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs Deterministic/idempotent masking for sensitive strings.
crates/cmtraceopen-parser/tests/intune_windows_scripts.rs Fixture-driven integration tests + golden export check.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/expected-full.json Golden redacted export for shape pinning.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/IntuneManagementExtension.log Fixture IME evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/IntuneManagementExtension.log Fixture IME evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/IntuneManagementExtension.log Fixture IME evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/IntuneManagementExtension.log Fixture IME evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/IntuneManagementExtension.log Fixture IME evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/AgentExecutor.log Fixture AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/AgentExecutor.log Fixture AgentExecutor evidence (multiline record).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/AgentExecutor-1.log Fixture rotated AgentExecutor evidence.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/AgentExecutor.log Fixture live AgentExecutor evidence (orphan tail).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/expected.json Fixture expectations (contract assertions).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/AgentExecutor.log Fixture AgentExecutor evidence (unknown wording).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/manifest.json Fixture scenario manifest.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/expected.json Fixture expectations + redaction needles.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/IntuneManagementExtension.log Fixture IME evidence (UPN present).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/AgentExecutor.log Fixture AgentExecutor evidence (cmdline/path secrets).
Suppressed comments (2)

crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs:217

  • If underscore-prefixed archive rotations are represented with rotation_ordinal = None, the corresponding unit test should assert None rather than Some(1).
    fn underscore_archive_form_is_recognised_as_a_rotation() {
        let (stem, ordinal) = split_rotation("_AgentExecutor.log");
        assert_eq!(stem, "AgentExecutor");
        assert_eq!(ordinal, Some(1));
    }

crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs:83

  • The fallback return path in split_rotation also assigns Some(1) for underscore-prefixed archive names. For archives without a trustworthy ordinal, this should be None to avoid implying an ordering that isn't present in the filename.
    (
        without_ext.to_string(),
        if underscore_archive { Some(1) } else { Some(0) },
    )

Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs Outdated
Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/models.rs Outdated
adamgell and others added 2 commits July 31, 2026 01:25
Code review of #386 surfaced six real defects. Each is fixed with a
regression test that fails without the fix.

1. Unconfirmed artifacts could still mint a transaction. `classify_record`
   extracted policy and run ids before consulting the source kind, so a file
   that merely happened to be named `AgentExecutor.log` -- one that
   `classify_artifact` had correctly refused to confirm -- could create a
   transaction from any line containing a GUID. This directly contradicted the
   guarantee in `sources.rs`. Identifiers are now extracted only from a
   confirmed AgentExecutor or primary IME source. HealthScripts is excluded for
   the mirror-image reason: it is remediation evidence, and letting it carry a
   key would attach remediation records to a platform-script transaction before
   #360 defines the handoff.

2. `normalized_utc` was not what its own doc comment claimed. `ime_parser`
   fills `timestamp_utc` from the *parsing machine's* local offset when a
   record carries none, which IME records usually do not. The exported value
   was therefore UTC-labelled but derived from whoever opened the log. Proof
   that this was user-visible: the previous golden file had this machine's
   timezone baked into it and would have failed for any developer elsewhere.
   `ImeLine` now carries `timezone_offset`, and `normalized_utc` is populated
   only when the record stated its own offset; `original_offset` reports it.

3. Confidence was demoted bundle-wide. A single unrecognised record anywhere in
   the supplied evidence dropped *every* transaction to low confidence,
   including fully evidenced ones for unrelated policies -- contradicting the
   field's own doc comment. The signal is now scoped to the transaction that
   contains the record. `unknown_version_observed` remains bundle-level, as
   coverage.

4. Multi-line records skipped command-line redaction entirely. The regex
   anchored on `$`, which does not match at a line break, so a `-Command` in a
   multi-line CCM record matched nothing and exported verbatim. Multi-line
   records are not hypothetical; a fixture already proves them.

5. Redaction covered only `-Command`/`-EncodedCommand`. `-Password`,
   `-ApiKey`, `-ClientSecret`, and similar credential switches exported their
   values in full. The vocabulary now covers them.

6. Transaction ordering ignored time even when time was trustworthy. Ordering
   decides which attempt a retry sequence reports. When every record in a
   transaction states its own offset, real time is now used; otherwise source
   order is kept, and the expectation that callers pass rotations oldest-first
   is written down rather than assumed.

Two fixtures were added: `multi-policy-partial-unknown-version` pins fix 3, and
`explicit-timezone-offset` pins fix 2 from the positive direction.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 397 unit, 222 esp integration,
  21 `intune_windows_scripts` integration, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

Refs #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three findings from Copilot's review of #386, all valid.

1. `_AgentExecutor.log` reported rotation ordinal `Some(1)`, which is
   indistinguishable from an explicit `-1` and would mislead ordering. The
   underscore prefix says a file is an archive, not which archive, so the
   ordinal is now `None` -- matching what the surrounding comment already
   claimed.

2. `report_sent_re` ended in the bare fragment `succe`, an accidental
   truncation that matched any word with that prefix. It now requires a
   complete success token with a word boundary.

3. `ScriptSourceKind::ScriptOutput` and `PolicyMetadata` were unreachable, and
   `ScriptTransaction::display_name` was always `None`. Retained output
   artifacts are named `{policyId}_{runId}.output` / `.error`, which is not a
   bare word any file could coincidentally carry -- it encodes both halves of a
   transaction key -- so they are now classified from that identity and satisfy
   the "retained script output artifact" evidence request for their own
   transaction. Their *contents* are deliberately never parsed: raw script
   stdout is unbounded and frequently sensitive, and the file name is the
   evidence being claimed.

   `PolicyMetadata` and `display_name` are removed rather than left dead. There
   is no supplied-metadata input contract in this issue, so neither could ever
   be populated, and a serialized field that is structurally always null is
   worse than an absent one.

Fixture `nonzero-exit-with-retained-artifact` pins the third fix: the same
bundle that asks for an output artifact when it is absent asks for the
reporting half instead once it is supplied.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 399 unit, 222 esp integration,
  22 `intune_windows_scripts` integration, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

Refs #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamgell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copilot reviewed 53 out of 77 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs:57

  • split_rotation returns Some(0) ("live" ordinal) even when the filename doesn’t end in .log/.LOG (e.g., retained {policyId}_{runId}.error / .output artifacts). That makes non-log artifacts incorrectly report rotationOrdinal: 0 in coverage, even though the doc comment says the ordinal is only present when the name identifies a rotation/log.
    let trimmed = file_name.trim();
    let without_ext = trimmed
        .strip_suffix(".log")
        .or_else(|| trimmed.strip_suffix(".LOG"))
        .unwrap_or(trimmed);

@coderabbitai coderabbitai 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.

Actionable comments posted: 7

🧹 Nitpick comments (4)
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs (1)

89-95: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Match artifact extensions case-insensitively.

The suffix checks accept only all-lowercase and all-uppercase forms. A file named {policyId}_{runId}.Output or .Error classifies as ScriptSourceKind::Unknown, and the reducer then loses the retained-output evidence for that run. Lines 40-43 have the same gap for .Log.

♻️ Proposed case-insensitive extension matching
/// Strip `suffix` from `value`, ignoring ASCII case.
fn strip_suffix_ignore_ascii_case<'a>(value: &'a str, suffix: &str) -> Option<&'a str> {
    let split = value.len().checked_sub(suffix.len())?;
    let (head, tail) = value.split_at(split);
    tail.eq_ignore_ascii_case(suffix).then_some(head)
}
-    let stem = trimmed
-        .strip_suffix(".output")
-        .or_else(|| trimmed.strip_suffix(".error"))
-        .or_else(|| trimmed.strip_suffix(".OUTPUT"))
-        .or_else(|| trimmed.strip_suffix(".ERROR"))?;
+    let stem = strip_suffix_ignore_ascii_case(trimmed, ".output")
+        .or_else(|| strip_suffix_ignore_ascii_case(trimmed, ".error"))?;

Apply the same helper to the .log check in split_rotation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs` around
lines 89 - 95, Make artifact extension matching ASCII-case-insensitive in
output_artifact_key and split_rotation. Add and reuse a
strip_suffix_ignore_ascii_case helper for .output, .error, and .log checks so
mixed-case extensions such as .Output, .Error, and .Log classify and rotate
correctly.
crates/cmtraceopen-parser/tests/intune_windows_scripts.rs (1)

148-208: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Optional fixture rules fail open on a key typo.

Each rule runs only when expected.get(...) returns a value. If a fixture misspells redactionMustNotContain, the leak assertion never runs and the test still passes. Consider asserting that every key present in expected.json belongs to a known set, so an unrecognised key fails the scenario.

♻️ Proposed guard against unknown expectation keys
+    const KNOWN_KEYS: [&str; 7] = [
+        "transactions",
+        "unkeyedObservationCount",
+        "coverage",
+        "messageMustContain",
+        "timestampMustBe",
+        "redactionMustNotContain",
+        "redactionMustContain",
+    ];
+    for key in expected.as_object().expect("expected must be an object").keys() {
+        assert!(
+            KNOWN_KEYS.contains(&key.as_str()),
+            "{scenario}: unknown expectation key {key:?}; a typo would skip its rule"
+        );
+    }
+
     if let Some(rule) = expected.get("messageMustContain") {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/tests/intune_windows_scripts.rs` around lines 148 -
208, Validate the fixture keys in the scenario expectation handling before
applying the optional rules in the test. Define the allowed expectation keys
used by the existing checks, including messageMustContain, timestampMustBe,
redactionMustNotContain, and redactionMustContain, and fail the scenario when
expected contains any unrecognised key.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs (2)

22-30: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Unsalted FNV-1a tokens are reversible for low-entropy values.

stable_token hashes the plaintext directly with a non-keyed 64-bit function. UPNs, profile names, and short inline secrets have low entropy. An attacker who holds an export can recover them with a dictionary attack, because the token depends only on the value.

Add a keyed or salted derivation. A build-time or configuration constant preserves cross-run determinism and keeps the expected-full.json golden stable. A per-export random salt gives a stronger guarantee, but it breaks the golden test and cross-export correlation, so choose the tradeoff explicitly and record it in the module docs.

♻️ Proposed keyed derivation
-/// FNV-1a. Stable across runs and platforms, which `DefaultHasher` is not.
-fn stable_token(kind: &str, value: &str) -> String {
-    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
-    for byte in value.as_bytes() {
+/// Keyed FNV-1a. Stable across runs and platforms, which `DefaultHasher` is
+/// not, and not invertible by dictionary attack without `REDACTION_KEY`.
+const REDACTION_KEY: &[u8] = b"cmtraceopen-script-redaction-v1";
+
+fn stable_token(kind: &str, value: &str) -> String {
+    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
+    for byte in REDACTION_KEY.iter().chain(value.as_bytes()) {
         hash ^= u64::from(*byte);
         hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
     }
     format!("[{kind}:{:016x}]", hash)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs`
around lines 22 - 30, Update stable_token to derive its digest with a fixed
keyed or salted mechanism instead of hashing value directly with unsalted
FNV-1a, preserving deterministic output and the expected-full.json golden.
Define the key or salt as a clear module-level configuration constant and
document the chosen deterministic tradeoff in the module docs; keep the existing
token format and kind handling unchanged.

62-70: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

The flag rule misses : and = separators and abbreviated parameters.

The pattern requires \s+ after the flag. -Command:Set-Secret hunter2 and -Password=hunter2 are both valid invocations and neither matches, so the value is exported verbatim. PowerShell also accepts unambiguous prefixes, so -enc <base64> and -Comm <script> bypass the rule.

Accept [:=\s] as the separator and cover the common abbreviations.

Note also that (?P<value>[^\r\n]+) is greedy to the line break. Any diagnostic token that follows the command on the same line is hashed into the token, which conflicts with the module claim that signals and exit codes survive. Confirm that the record grammar never places an exit code after an inline command on one line.

♻️ Proposed separator and vocabulary widening
-            r"(?i)(?P<flag>-(?:Command|EncodedCommand|Password|Secret|ClientSecret|Token|AccessToken|ApiKey|Api[-_]?Key|Credential|Authorization)\s+)(?P<value>[^\r\n]+)",
+            r"(?i)(?P<flag>[-/](?:Comm(?:a(?:n(?:d)?)?)?|Enc(?:o(?:d(?:e(?:d(?:C(?:o(?:m(?:m(?:a(?:n(?:d)?)?)?)?)?)?)?)?)?)?)?|Password|Secret|ClientSecret|Token|AccessToken|ApiKey|Api[-_]?Key|Credential|Authorization)[:=\s]\s*)(?P<value>[^\r\n]+)",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs`
around lines 62 - 70, Update command_line_re to accept colon, equals, or
whitespace separators and recognize unambiguous common PowerShell abbreviations
such as -enc and -Comm alongside existing parameters. Review the record grammar
to determine whether an exit code or diagnostic token can follow an inline
command; if so, narrow the value capture so those trailing fields remain
available, otherwise preserve the current line-bounded capture only when the
grammar guarantees no such suffix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs`:
- Around line 41-49: Update user_path_re to allow spaces within the captured
user segment, while still excluding path separators, line breaks, quotes, and a
leading “[” so masking remains idempotent. Ensure the replacement re-emits the
matched end boundary as needed, and add a fixture covering a spaced Windows
profile name. Also verify whether down-level names such as CONTOSO\user occur in
classified records and extend upn_re only if that format is in scope.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs`:
- Around line 369-385: Classify script-output artifacts by name before parsing
their contents. Add the proposed public candidate_source_kind function in
sources.rs, delegating to candidate_from_name, then update the input loop to use
it before parse_ime_content; when it identifies ScriptSourceKind::ScriptOutput,
register output_artifact_identity and continue without parsing, while preserving
the existing content-based classify_artifact path for other inputs.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs`:
- Around line 246-252: Update the hex rendering in the decimal parsing branch
around signed and hex_text so hex_text is produced only when the parsed value
fits the unsigned 32-bit view; avoid casting out-of-range values to u32 and
returning a truncated representation. Preserve the signed decimal value while
leaving hex_text absent for values outside the valid range.
- Around line 417-438: Gate IME classification in the classify_record/reduce
flow on a script-scope marker such as component="PowerShell", so non-script
Win32App records cannot emit ScriptSignal values or policy keys. Preserve
existing classify_ime behavior for records that are in script scope, and add a
fixture covering an equivalent Win32App record without the marker.

In `@crates/cmtraceopen-parser/src/intune/ime_parser.rs`:
- Around line 20-26: Apply the timezone trust gate to every consumer of the
parsed timestamp: update the timestamp selection in download stats and event
tracking so records with timezone_offset == None do not use machine-derived
timestamp_utc for ordering or comparisons. Reuse the source-offset presence
check and preserve timestamp_utc only when its source timezone is trustworthy;
otherwise use each consumer’s existing safe fallback or skip the timestamp-based
operation.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/expected.json`:
- Around line 7-13: Update the failedToLaunch fixture’s nextEvidenceRequest to
request relevant AgentExecutor.log context or an IntuneManagementExtension.log
record instead of retained script output. Preserve lastConfirmedPhase as
"launched" and leave the other expected fields unchanged.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/manifest.json`:
- Line 2: Update the manifest description for the nonzero-exit-with-output
fixture to describe evidence from an in-log captured-output record, not a
retained error artifact. Keep the description aligned with the provided
AgentExecutor.log and distinguish it from the
nonzero-exit-with-retained-artifact scenario.

---

Nitpick comments:
In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs`:
- Around line 22-30: Update stable_token to derive its digest with a fixed keyed
or salted mechanism instead of hashing value directly with unsalted FNV-1a,
preserving deterministic output and the expected-full.json golden. Define the
key or salt as a clear module-level configuration constant and document the
chosen deterministic tradeoff in the module docs; keep the existing token format
and kind handling unchanged.
- Around line 62-70: Update command_line_re to accept colon, equals, or
whitespace separators and recognize unambiguous common PowerShell abbreviations
such as -enc and -Comm alongside existing parameters. Review the record grammar
to determine whether an exit code or diagnostic token can follow an inline
command; if so, narrow the value capture so those trailing fields remain
available, otherwise preserve the current line-bounded capture only when the
grammar guarantees no such suffix.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs`:
- Around line 89-95: Make artifact extension matching ASCII-case-insensitive in
output_artifact_key and split_rotation. Add and reuse a
strip_suffix_ignore_ascii_case helper for .output, .error, and .log checks so
mixed-case extensions such as .Output, .Error, and .Log classify and rotate
correctly.

In `@crates/cmtraceopen-parser/tests/intune_windows_scripts.rs`:
- Around line 148-208: Validate the fixture keys in the scenario expectation
handling before applying the optional rules in the test. Define the allowed
expectation keys used by the existing checks, including messageMustContain,
timestampMustBe, redactionMustNotContain, and redactionMustContain, and fail the
scenario when expected contains any unrecognised key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 96a1bba8-9709-42bf-8d0a-fba5a905f183

📥 Commits

Reviewing files that changed from the base of the PR and between bc2cd1f and 3522e3d.

⛔ Files ignored due to path filters (24)
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/explicit-timezone-offset/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multi-policy-partial-unknown-version/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-retained-artifact/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/AgentExecutor-1.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/IntuneManagementExtension.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/AgentExecutor.log is excluded by !**/*.log
📒 Files selected for processing (53)
  • crates/cmtraceopen-parser/src/intune/apps/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/models.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs
  • crates/cmtraceopen-parser/src/intune/download_stats.rs
  • crates/cmtraceopen-parser/src/intune/event_tracker.rs
  • crates/cmtraceopen-parser/src/intune/guid_registry.rs
  • crates/cmtraceopen-parser/src/intune/ime_parser.rs
  • crates/cmtraceopen-parser/src/intune/mod.rs
  • crates/cmtraceopen-parser/src/intune/policy_parser.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/execution-success-report-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/explicit-timezone-offset/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/explicit-timezone-offset/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multi-policy-partial-unknown-version/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multi-policy-partial-unknown-version/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/multiline-ccm-record/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-retained-artifact/11111111-1111-4111-8111-111111111111_aaaaaaaa-1111-4111-8111-aaaaaaaaaaa1.error
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-retained-artifact/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-retained-artifact/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-without-output/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/policy-received-no-execution/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/privacy-redaction/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/retries-exhausted/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/rotation-boundary/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/expected-full.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-device-context/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/success-user-context/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/three-retries-then-success/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/timeout/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/two-scripts-same-minute/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/unknown-agent-version/manifest.json
  • crates/cmtraceopen-parser/tests/intune_windows_scripts.rs

Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
Comment thread crates/cmtraceopen-parser/src/intune/ime_parser.rs
Six of seven CodeRabbit findings were valid and are fixed here. The seventh is
answered rather than implemented; see the end.

1. A Windows profile name containing a space was only partially masked. Both
   halves of the `user` capture excluded `\s`, so `C:\Users\John Doe\...`
   exported as `C:\Users\[user:...] Doe\...` and leaked the surname. The
   segment is now bounded by the path separator, a quote, or the line break.
   This is the most serious finding in the batch: it is a live privacy leak on
   a path Windows permits.

2. Retained output artifacts were parsed before being skipped. The module
   promises never to read their contents -- they are raw script stdout, which is
   unbounded and often sensitive -- but the skip ran after `parse_ime_content`
   had already built an owned line vector for the whole file. Classification is
   now name-first, so the promise is kept in fact and not only in the comment.

3. An exit code outside the u32 range rendered a truncated hex view:
   `4294967297` printed as `0x00000001`, a different number than the log
   recorded. Out-of-range values now carry no hex form at all.

4. The primary IME log is shared by every workload, and record classification
   did not check the component. A `Win32App` record mentioning "Get policies"
   and a policy GUID produced a platform-script signal and a policy key --
   inventing a script transaction out of app-deployment evidence. Record
   signals now require a script-scope component. `Win32App` still confirms that
   the *file* is the IME log; it just cannot speak for this workload.

5. `FailedToLaunch` asked for a "retained script output artifact". A process
   that never started cannot have written one, so this sent an operator after
   an artifact that does not exist. It now asks for the surrounding launch
   context.

6. The `nonzero-exit-with-output` manifest described a retained error artifact
   it does not contain; that belongs to `nonzero-exit-with-retained-artifact`.
   Corrected.

Not implemented: CodeRabbit also observed that `download_stats` and
`event_tracker` still read `timestamp_utc` unconditionally and stay
machine-timezone dependent for offset-less records. That is true and
pre-existing. `ImeLine::timezone_offset` reports provenance and does not change
`timestamp_utc`, so nothing here regresses those paths. Changing how the Intune
workspace orders records is a behavioural change that needs its own commit and
its own regression coverage, not a field addition. The contract is narrowed in
the field's documentation instead, which is the alternative CodeRabbit offered.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 404 unit, 222 esp integration,
  22 `intune_windows_scripts` integration, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.
- `git diff --check`: clean.

Refs #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamgell
adamgell requested a review from Copilot July 31, 2026 05:48
@adamgell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs`:
- Around line 365-367: Prevent out-of-scope records rejected by
component_is_in_scope in the rules classification flow from being counted as
unclassified_records by reducer.rs, while preserving existing in-scope
classification behavior. Propagate scope status through RecordClassification or
filter these records before the reducer increments the counter. Add an
integration test covering a Win32App IME record and verify it does not increase
script coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d23cb3c-c847-4987-9a09-2c5902de8c20

📥 Commits

Reviewing files that changed from the base of the PR and between 3522e3d and 00512a1.

📒 Files selected for processing (8)
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs
  • crates/cmtraceopen-parser/src/intune/ime_parser.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/manifest.json
🚧 Files skipped from review as they are similar to previous changes (6)
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/nonzero-exit-with-output/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/scripts/launch-failure/expected.json
  • crates/cmtraceopen-parser/src/intune/ime_parser.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs

Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/scripts/rules.rs

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

Copilot reviewed 53 out of 77 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs:170

  • rotation_ordinal is derived via split_rotation() for every artifact, including ScriptSourceKind::ScriptOutput. For retained .output/.error artifacts this will currently report Some(0) ("live file") even though these are not rotated .log files, which can mislead consumers interpreting rotation_ordinal as log-rotation metadata.
    let candidate = candidate_from_name(&input.file_name);
    let (_, rotation_ordinal) = split_rotation(&input.file_name);

Two findings from the second review cycle on #386, both valid.

1. Out-of-scope records inflated platform-script coverage. The previous commit
   correctly stopped `Win32App` records in the shared IME log from producing a
   script signal, but the reducer then counted those empty classifications in
   `unclassified_records`. Because the primary IME log carries every workload,
   the effect was that a busier device reported worse platform-script coverage
   than a quiet one, for reasons having nothing to do with scripts.
   `RecordClassification` now carries `in_scope`, and only in-scope records can
   be counted as unclassified script records.

2. A transaction could report a stale exit code. `exit_token` was only
   overwritten when a completion record carried a readable code, so a later
   completion we could not parse left the earlier attempt's code in place. The
   state in that situation is `exitedNonZero` -- a completion whose code is
   unreadable cannot claim success -- so the transaction displayed
   `exitedNonZero` next to exit code `0`, a contradiction the evidence does not
   support. The assignment is now unconditional, including `None`.

Two fixtures pin the behaviour. `shared-ime-log-other-workload` puts three
`Win32App` records in the same IME log as a real script transaction and asserts
that script coverage stays at zero. `later-completion-without-readable-code`
runs two attempts where the second completion has no readable code and asserts
the earlier `0` does not survive.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 405 unit, 222 esp integration,
  24 `intune_windows_scripts` integration, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

Refs #359

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamgell
adamgell requested a review from Copilot July 31, 2026 05:57
@adamgell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copilot reviewed 57 out of 84 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/cmtraceopen-parser/src/intune/apps/windows/scripts/sources.rs:170

  • classify_artifact always derives rotation_ordinal via split_rotation, even when the candidate is ScriptOutput. For retained {policyId}_{runId}.output/.error artifacts, split_rotation will default to Some(0) ("live"), which is misleading because these artifacts are not rotated logs and their name does not encode an ordinal. This can confuse consumers that display or sort artifacts based on rotation_ordinal.
    let candidate = candidate_from_name(&input.file_name);
    let (_, rotation_ordinal) = split_rotation(&input.file_name);

@adamgell

Copy link
Copy Markdown
Owner Author

Independent verification of the timezone-dependent golden, plus one CI caveat.

The TZ bug was real, and f09632ca fixes it correctly.

Reproduced at 765a385f: under TZ=UTC, redacted_export_matches_the_golden failed — the golden encoded "normalizedUtc": "2026-03-12T14:15:20.000Z" (a UTC-4 authoring machine) while the analyzer emitted 10:15:20.000Z. The fixture CCM records carry time="10:15:20.0000000" with no offset, so ime_parser was normalizing against the host local zone.

f09632ca fixes it at the right level rather than by regenerating the golden:

original_offset: line.timezone_offset.map(format_offset),
normalized_utc: line.timezone_offset.and(line.timestamp_utc.clone()),

UTC is now only claimed when the source actually carried an offset, and the golden reads null. That is what the issue asks for ("use UTC only for ordering when the offset is valid"), and it closes the original_offset: None gap at the same time.

Verified at f09632ca across seven zones spanning UTC-11 to UTC+14 — 24/24 in every one:

UTC                24 passed   Australia/Sydney     24 passed
America/New_York   24 passed   Pacific/Kiritimati   24 passed
Asia/Tokyo         24 passed   Pacific/Midway       24 passed
Europe/Berlin      24 passed

Full crate under TZ=UTC: lib 405, esp_diagnostics 222, intune_windows_scripts 24, 1 doctest, 0 failed. clippy --all-targets -D warnings clean. wasm32-unknown-unknown check clean.

Caveat: CI is not actually running these 24 tests. .github/workflows/cmtrace-ci.yml on this branch runs only:

cargo test --locked -p cmtraceopen-parser --test esp_diagnostics

so intune_windows_scripts executes nowhere in CI, and the green check on this PR does not cover it. The src-tauri-scoped cargo test job cannot reach it either, since the parser crate is a separate workspace member. #389 changes that step to run the whole crate; once it lands, this suite starts being enforced — which is fine, because it now passes, but until then the signal here is hollow.

@adamgell adamgell added feature New feature intune Microsoft Intune related parser Log parser related apps App management related enhancement New feature or request labels Jul 31, 2026
@adamgell
adamgell merged commit f59094a into main Jul 31, 2026
17 checks passed
adamgell added a commit that referenced this pull request Aug 1, 2026
)

* refactor(intune): give the Windows analyzers one masking implementation

Before: `intune::apps::windows::scripts::redaction` owned the UPN, user-profile
and command-line masking regexes outright. A second Windows workload analyzer
needs exactly the same masking, and copying those four regexes would create two
implementations that drift apart -- the class of duplication that already drew
review comment on #386.

This moves the masking primitives to `intune::apps::windows::common`, unchanged.
`scripts::redaction` now calls into it and keeps deciding *what* is sensitive,
which is the part that genuinely differs per analyzer. `scripts::redact_text`
stays exported from the same path it was, so no consumer moves.

This is the right seam because the split is between two different kinds of
decision. Which spans of free text are dangerous is a property of Windows log
text in general. Which fields carry that text is a property of one analyzer's
contract. Only the first is shared.

Verified with `cargo test --locked -p cmtraceopen-parser`: 24
`intune_windows_scripts` integration tests pass unchanged, including the
redaction and golden-export cases, plus 407 unit and 222 esp tests. 0 failed.
`cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings` is
clean.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(intune): add the remediation detection/remediation analyzer

Before: Intune Remediations had no owner. `intune/event_tracker.rs` recognised
a `HealthScripts` source kind and emitted loose events, but nothing modelled the
pair. There was no way to answer whether detection ran, whether remediation was
required, whether it ran, what it did, or whether any of it was reported.

This adds `cmtraceopen_parser::intune::apps::windows::remediations`, a semantic
analyzer over supplied IME evidence:

- `sources.rs` classifies each artifact from its name *and* the CCM components
  inside it, and recognises retained `{policyId}_{runId}.output` / `.error`
  artifacts by name without ever reading their contents;
- `rules.rs` classifies one record at a time and decides nothing;
- `reducer.rs` groups records into pairs keyed on policy, run, and invocation,
  reducing detection and remediation into *separate* stage outcomes;
- `redaction.rs` masks record text, artifact paths, and embedded payloads.

The rule everything else rests on: **an exit code with no stage terminates
nothing.** `0` means "compliant" from a detection script and "succeeded" from a
remediation script. Attributing a code to the wrong half inverts the diagnosis,
so a record that does not name its stage yields no exit token at all, and an
`AgentExecutor.log` supplied without its `HealthScripts.log` orchestrator
produces no transactions rather than guessed ones. `exit_tokens_never_cross_stages`
asserts this across four scenarios, and `RemediationExitToken::stage` is not
optional, so an unattributed token cannot be constructed.

Invocation is part of the key, so a scheduled run and an on-demand run of the
same policy never pool. `Skipped` is a distinct remediation state from
`NotStarted`: it is the stronger claim that detection proved there was nothing
to do.

Embedded JSON payloads are preserved losslessly and marked parsed or not. A
malformed payload is reported as malformed, never repaired, and a payload whose
braces do not close inside one record is not captured at all -- fragments are
never concatenated across records to complete one.

One defect was found by these fixtures and fixed in the shared masking: a
Windows path inside a JSON payload arrives escaped as `C:\\Users\\Someone`, and
the profile-segment rule required a single separator, so every such path was
exported unmasked. Remediation payloads are the most likely place for that to
happen, since detection scripts emit arbitrary data.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 434 unit (was 407), 22 new
  `intune_windows_remediations` integration, 24 scripts, 27 company-portal,
  222 esp, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

All 17 scenarios required by the issue are present under
`tests/fixtures/intune/windows/remediations/`, each with a manifest, its
evidence, and a written-out `expected.json`.
`detection-compliant-remediation-skipped` additionally carries
`expected-full.json`, a golden of the complete redacted export.

This deliberately does not use `Closes`. The issue also asks that the relevant
`HealthScripts`/`AgentExecutor` logic in `intune/event_tracker.rs` be
consolidated behind the canonical API. That is not done here, so two rulesets
still exist in the crate; the same gap is tracked on #359.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(intune): close four defects found by review of the remediation analyzer

1. A block never closed, so a stray record on a reused thread could overwrite a
   terminal outcome. Blocks only split at a stage launch, and nothing ended one
   when a run reported. The agent reuses CCM thread numbers freely, so a later
   record that named no policy of its own was absorbed into whatever block was
   still open, inherited that run's key and stage, and its exit code was applied
   to a transaction it had nothing to do with -- silently inverting a diagnosis
   with no conflict raised, because the record never stated a policy to conflict
   with. Blocks now also open at a policy receipt and close after a report.
   Fixture `thread-reuse-after-report` pins it.

2. A comment promised a safety net that did not exist. It claimed that "two
   policies, or two stages" in one block would refuse to key, but only the
   policy conflict returned early. The behaviour on a stage conflict is
   intentional -- the records share a policy, so the key is safe; the stage is
   not, so it is withheld and those records stay unstaged -- but the comment
   described something else entirely. Corrected to say what the code does and
   why the two conflicts are handled differently.

3. Two different constants named `IME_COMPONENTS` lived in the same module with
   different contents: one gating which records may speak for this workload,
   one confirming that a file is the IME log at all. Renamed to
   `IME_RECORD_SCOPE_COMPONENTS` and `IME_FILE_COMPONENTS`, since the wider file
   list looked like a bug next to the narrower record list.

4. The module's most subtle rule was undocumented and read as an accident: a
   record may inherit its block's stage for *routing* but never for
   *interpreting* an exit code. Spelled out in the module docs with the reason
   -- routing a record to the wrong stage costs an unresolved half, while
   interpreting a code under the wrong stage turns "compliant" into "succeeded"
   -- and pinned with a test.

Reviewed and rejected: that `PowerShell` be accepted as a remediation-scope
component in the primary IME log, on the grounds that the sibling script
analyzer accepts it there. It is exactly because the sibling owns `PowerShell`
in that log that this module must not: accepting it would make platform-script
reporting records become remediation reporting records, which is the
cross-workload contamination the scope gate exists to prevent. Remediation
orchestration is logged under `HealthScripts`. Two tests now pin both
directions, and the reasoning is in the constant's documentation.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 437 unit, 23
  `intune_windows_remediations`, 24 scripts, 27 company-portal, 222 esp, 1 doc
  test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.
- `git diff --check`: clean.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(intune): close nine review findings on the remediation analyzer

All nine findings from Copilot and CodeRabbit were valid.

1. An IME log whose remediation records all carry `component="HealthScripts"`
   was classified `Unknown` and never reached the reducer, because the
   file-confirmation list omitted the one component this workload actually
   uses there. Added.

2. A record naming an output path was treated as proof that the output artifact
   was supplied. It is not: it proves the script wrote one somewhere on the
   device. Using it to suppress the evidence request hid genuinely missing
   artifacts. Only an artifact actually present in the bundle counts now.

3. Credential values separated by `:` or `=` were exported verbatim.
   `-Password:hunter2` and `-Token=abc` are as common as the spaced form.

4. A detection that failed or timed out *with* output evidence fell through to
   the remediation branch and asked for remediation records -- contradicting the
   confidence rule in the same file, which treats a terminal detection as a
   complete story where remediation is legitimately absent. A terminal
   detection now always ends the request chain.

5. A later completion with no readable exit code erased a determined result.
   Both stage appliers overwrote state unconditionally and mapped a missing
   code to `InsufficientEvidence`, so a trailing generic "Detection script
   finished" discarded what a readable code had already proved. Such a record
   now contributes evidence without downgrading the state.

6. `script_path_re` captured `detect|remediate` and the captured group was
   thrown away. A path such as `...\{policy}_{run}\detect.ps1` therefore named
   no stage, because the prose rules require the word `script` to follow. The
   path states the stage as plainly as the prose does; it is now used.

7. `hex_text` was copied from the source text rather than rendered from the
   parsed value, so the same exit code rendered two ways (`0x1` against
   `0x00000001`), a negative hex code lost its sign, and an unreadable
   oversized literal still produced a hex form while `decimal` was `None`.

8. The payload brace scan counted braces inside JSON string literals, so
   `{"Detail":"a } b","Compliant":false}` was truncated to `{"Detail":"a }` and
   then reported as malformed -- losing the evidence the module exists to keep
   and misreporting coverage. String and escape state are now tracked.

9. Extension matching enumerated fixed casings, so `HealthScripts.Log` and
   `{policy}_{run}.Output` classified as `Unknown`. Windows file names are
   case-insensitive; one shared helper now handles both sites.

Findings 7 and 8 are the two most likely to bite in practice, because detection
scripts emit arbitrary text into payload fields and vendors write exit codes in
either base.

Three fixtures were added for the behaviour changes that no existing scenario
covered: `detection-timeout-with-retained-artifact` (finding 4),
`later-completion-without-readable-code` (finding 5), and the case-insensitive
and IME-confirmation cases as unit tests (findings 1 and 9).

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 446 unit, 25
  `intune_windows_remediations`, 24 scripts, 27 company-portal, 222 esp, 1 doc
  test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.
- `git diff --check`: clean.

Note for follow-up: finding 7 describes the same `hex_text` derivation that
exists in the already-merged `apps::windows::scripts::rules`. That module is not
touched here; it should get the same fix.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apps App management related enhancement New feature or request feature New feature intune Microsoft Intune related parser Log parser related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants