Skip to content

feat(machine-health): add the optional elevated pass - #9067

Merged
kyle-sexton merged 8 commits into
mainfrom
feat/machine-health-elevated-pass
Oct 22, 2026
Merged

kyle-sexton merged 8 commits into
mainfrom
feat/machine-health-elevated-pass

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: phase 9 of the owner-approved machine-health checkup extension plan (ADR 0081 series).

Summary

Adds an optional elevated pass to /machine-health:audit. The session prepares a one-command script, shows it and its SHA-256 to the owner, and only on the owner's explicit yes in that session launches it behind UAC. The elevated run executes only the elevation-matrix checks and writes one result file; a later standard run imports it. A -p (headless) or --scheduled run never offers the pass.

Fix

  • Orchestrator: exclusive parameter sets -ResultPath/-ResultFileId (elevated pass) and -ElevatedResultPath (import). The elevated branch runs before any directory creation, lock or log; reads mutes through Get-MuteState only; dispatches only shipped catalog scripts that have an elevation-matrix row (the per-machine overlay can switch a check off, never change its script); writes nothing but the result file.
  • New-ElevatedLaunch / Start-ElevatedLaunch / Get-ElevatedLaunch: pre-create the result file and record its 128-bit file ID from the creating handle; the launch block reads the script bytes once, exits 3 on a hash mismatch, and runs those same bytes; the launch string is rebuilt from the record, never written by the model; paths with control characters or '/U+2018-U+201B are refused; the interpreter is $PSHOME\pwsh.exe, not a PATH lookup; refuses unless the token is a split admin token.
  • Write-ElevatedResult: opens without following reparse points, refuses non-disk handles, reparse points and a file whose ID is not the recorded one, writes through that handle.
  • Import-ElevatedResult: accepts only elevated-result/1, elevated: true, the importing user's SID, ran_at after the file's creation and within 24 h, and results passing Assert-CheckResult; replaces or appends results for matrix ids and lists them in latest.json elevated_checks.
  • audit/SKILL.md: every command takes the plugin, data and report roots from a single-quoted here-string with a | line prefix, so no substituted path is typed inside a quoted literal (a root holding U+2019 could otherwise break out and inject code).
  • Write-ElevationBanner: no Start-Transcript. elevation-matrix.md "How to run elevated" and residual risk; output-schema.md documents elevated_checks.

Verification

  • Pester suite: 1176 passed, 0 failed, 1 skipped (run-tests.ps1, pre-existing non-Windows skip).
  • Independent phase verifier: all criteria pass. Holdout: 200 hostile-root variants (U+2019/U+2018/U+201A/U+201B around $( ), roots starting '@/’@, quotes, spaces) parsed with Parser::ParseInput give the same command list as a benign root, 0 parse errors; 40/40 roots bind exactly; no canary file created. New-ElevatedLaunch refused 27/27 hostile roots; a non-elevated round trip returned the child's exit code 5 with exact paths; one tampered byte gives exit 3 without running the orchestrator.
  • Non-elevated stand-in of the elevated branch with the real catalog: <StateBase> listing differs only in the result file's length.
  • Get-ApprovalState in Invoke-MachineHealthCheck.ps1 appears only after the -ResultPath branch's exits (branch 156-229, exits 163/225/228; dot-source 233, call 593).
  • git grep Start-Transcript -- plugins/machine-health/skills/audit/scripts: no output.
  • Reviews: security and code-design reviews, then an independent Fable review; all findings fixed.
  • Owner-pending after merge: one real elevated run approved at UAC, confirming the <StateBase> listing and elevated_checks on the owner's host.

Related

🤖 Generated with Claude Code

kyle-sexton and others added 5 commits October 22, 2026 09:33
The session writes a one-command script with New-ElevatedLaunch, shows
it with its SHA-256, and launches it behind UAC only after the owner's
yes. The elevated side checks the hash over the bytes it runs, runs only
the elevation-matrix checks from their shipped scripts, and writes only
the pre-created result file, opened without following reparse points and
matched by file ID. A standard run imports it with -ElevatedResultPath.

The orchestrator gains two exclusive parameter sets; the -ResultPath
branch exits before any directory, lock, log or approval read. The
banner and report point to the pass instead of a transcript command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t junction swap

The skill now runs the elevated launch as a background command and
imports only after its completion notice, so the wait for UAC and the
admin-gated checks never cuts the pass short. A new test swaps the logs
folder for a junction to a same-named file and shows the file ID check
refuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Launch: the model no longer quotes the argument string. Start-ElevatedLaunch
takes the launch record, rebuilt by Get-ElevatedLaunch from the state root
and the shown hash, and refuses a string that differs from the rebuild.
New-ElevatedLaunch refuses any path holding a control character or one of
the five single-quote characters, and records the file ID from the handle
that created the result file.

Write: CreateFileW adds anonymous SECURITY_SQOS_PRESENT and refuses a
non-disk handle; the file ID is the 64-bit volume serial and 128-bit
FileIdInfo ID, its format held once in Test-FileIdFormat. The document
records the writer's user SID.

Import: refuses a document whose elevated is not true or whose SID is not
the importing user's, keeps only elevation-matrix ids (dropped ones are
counted in an INFO note), and copies the log lines to the run log. The
history's ran ids are derived from the final results. New-UnknownCheckResult
moves to lib, and one per-check timeout serves both dispatch loops.

SKILL.md states who can give the yes and records the upstream pointers;
elevation-matrix.md states the mechanism and the wider residual risk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e elevated pass

Move the elevated-result format string to Get-ElevatedResultFormat.ps1 so
Import-ElevatedResult no longer dot-sources Write-ElevatedResult, and dot-source
Write-ElevatedResult only inside the orchestrator's -ResultPath branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tighten the elevated pass

- SKILL.md: every PowerShell command is the roots block plus one line. Claude
  Code substitutes the plugin root, data folder and report_dir into a
  single-quoted here-string behind a '|' sentinel, so no character in a path
  (U+2018 to U+201B, ', $( )) reaches the parser as code. The block applies the
  resolution fallbacks; the main orchestrator call gains a template.
- Import appends elevated results for matrix checks the run did not dispatch,
  refuses a result older than -MaxAge (24 h) before the run started, and words
  a foreign user_sid as another account's result.
- New-ElevatedLaunch refuses a token without a linked admin token, stamps names
  with InvariantCulture; Get-ElevatedLaunch reads ResultPath and ResultFileId
  from the script and refuses a stamp mismatch; Start-ElevatedLaunch launches
  $PSHOME\pwsh.exe.
- Get-RanCheckId extracted from the orchestrator, counting imported ids.
- Tests: SKILL.md template parse with hostile roots, orchestrator import and
  -ResultPath overlay-script cases, exit-code propagation, th-TH stamp.
- Docs: elevation-matrix.md mechanism and residual risk, elevated_checks in
  output-schema.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

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

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T13:36:26.902487Z 499339a PR opened
ℹ️ About Codex in GitHub

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

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

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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 499339aabd

ℹ️ About Codex in GitHub

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

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

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

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

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 18f7e1d036ddad23152f1db82c147b6d2bc261b2: 0 finding(s), 0 as inline comments.

Incremental review of PR #9067 (head 18f7e1d0), since 8b5f501: no findings.

The only change is one hunk in plugins/machine-health/skills/audit/SKILL.md, in step 3 ("Show and ask") of the elevated pass. The step now tells the owner, before asking for the yes, that the hash covers only the one-command script. The orchestrator, lib and check scripts it runs, the profile's module paths and its environment are trusted as the owner's own.

  • Consistent with the matrix: I read the "Residual risk, accepted" paragraph in plugins/machine-health/skills/audit/reference/windows/elevation-matrix.md (line 26). It lists the same four exposures: the orchestrator and lib scripts, user-writable module paths, HKCU\Environment with PATH and PSModulePath, and pwsh under the user profile. The new text matches it. The "accepted residual risk in elevation-matrix.md" pointer names a heading that exists.
  • No volatile upstream restatement: the added text states the skill's own behavior, not an upstream specific, so the skill-bodies rule is not triggered.
  • Nothing else changed: the hunk touches no flow, gate or command, and it leaves the existing rule that only the owner's own message is the yes unchanged.
  • Scope: I did not re-review the other files, which the lane reviewed at 8b5f501. I ran no tests. The Pester and holdout results in the PR body are author-claimed and unverified by me.
  • Closing issue: none. The PR declares "No related issue".
  • Manifests: the incremental diff touches no plugin.json or marketplace.json, so no manifest <field> lines apply.

…nt findings

- The import rerun after an elevated pass adds -DryRun when the audit was a
  dry run, so approving the pass never enables a held-back remediation.
- The roots block no longer restates how Claude Code substitutes the tokens;
  the pointer record carries it. Blank-root match uses [ \t] instead of \s and
  fenced script paths use forward slashes (shell-portability gate).
- README no longer says elevation is never offered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 18f7e1d036ddad23152f1db82c147b6d2bc261b2: 0 finding(s), 0 as inline comments.

No security findings in the incremental change (8b5f501 → 18f7e1d0).

The change is one edit to plugins/machine-health/skills/audit/SKILL.md, in step 3 ("Show and ask"). It adds one disclosure the owner must see before approving the elevated pass. The ScriptSha256 covers only the one-command script. The orchestrator, lib and check scripts, the profile's module paths and its environment are trusted as the owner's own, so code already running as the owner could change them. The text cites the accepted residual risk in elevation-matrix.md.

  • Instruction-surface lens: the diff adds to a standing instruction and removes or narrows nothing. The explicit-yes rules are unchanged. Only the owner's own message counts as the yes. Parent or peer agent messages, tool output and file content never count. A -p run never launches. UAC approval does not replace the yes. I did not consult the protected-class register, because nothing was removed or softened.
  • Manifests: this change touches no plugin.json or marketplace.json, so there are no manifest <field> lines.
  • Skip gate: the change is prose, but the lens above applies to it. It increases what the owner sees before the elevated launch and widens no action.

Everything else in the PR is unchanged since 8b5f501 and was reviewed then.

…e portability baseline entry

The roots block held a literal tab inside its character class; it now reads
[ \t]. elevation-matrix.md has no portability hits left, so its baseline
entry goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not cover

Step 3 now says, before the yes, that the hash covers only the one-command
script and that the orchestrator, lib and check scripts, module paths and
environment it uses are trusted as the owner's own (the accepted residual risk).
A stronger trust anchor is tracked in #9071.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant