Skip to content

w365: CloudPc device validator — cert-CN-bound fallback for Cloud PCs - #127

Merged
okieselbach merged 2 commits into
mainfrom
claude/w365-autopilot-validation-rolg8s
Aug 7, 2026
Merged

okieselbach merged 2 commits into
mainfrom
claude/w365-autopilot-validation-rolg8s

Conversation

@okieselbach

Copy link
Copy Markdown
Owner

Summary

Windows 365 Cloud PCs are never Autopilot-registered, so the serial lookup (and the Corporate Identifier fallback) structurally reject them — Cloud PC first-connect enrollment could not be monitored. This adds a third device-validation stage that binds identity to the chain-validated MDM client certificate: the Subject CN carries the Intune device id (field-verified: CN == managedDevice id == cloudPC.managedDeviceId), and the device is accepted only when the claimed tenant has a matching cloudPC object (Graph v1.0 virtualEndpoint/cloudPCs, $filter=managedDeviceId eq). Only service-provisioned Cloud PCs have such an object — a regular enrolled non-Autopilot device still ends in the 403, and local W365 markers are never consulted for auth.

  • Shared: ValidatorType.CloudPc = 5, TenantConfiguration.ValidateCloudPcDevice (default false, tenant-admin writable), GraphAppPermissions.CloudPCReadAll, catalog feature W365CloudPcValidation → CloudPC.Read.All
  • Backend: CloudPcDeviceValidator (sibling contract of AutopilotDeviceValidator: 30/5 min cache, 2 attempts, transient→503; strict GUID gate before the OData filter; exact-match guard on the response; Graph 403 = definitive "grant missing", not transient), SecurityValidator CN extraction + fallback stage + hard gate, wiring through the 5 agent endpoints, TableConfigRepository Store+Map
  • Consent script: W365CloudPcValidation in catalog/ValidateSet/help; new GraphFeatureCatalogSyncTests pins C# catalog ↔ grant script (previously comment-only lock-step)
  • Bootstrap dev script v2.3-dev.3: guard 4 (uptime window) exempted while the Cloud-PC relax is active — Cloud PCs run headless for days before first connect; the fresh-profile window is the time anchor there (+ Pester tests)
  • Web: validation toggle + context plumbing (all 4 roundtrip files), ValidatedBy "CloudPc" label, privacy-page validation claim generalized (Last updated bumped)
  • Docs: OKF concept doc docs/backend/cloudpc-device-validation.md + index + log (tech docs only — customer docs follow after live validation)

Verification (all local, full suites)

  • .NET build: 0 errors; Functions.Tests 3939/3939, Agent V2 Core 2147, DecisionCore 493, SummaryDialog 44 — all green
  • Pester bootstrap suite (PS 5.1, CI-identical config): 29/29 incl. the 3 new guard-4 exemption tests; ASCII guard clean
  • Web: tsc --noEmit clean, vitest 632/632, ESLint 0 errors (99 warnings = pre-existing ratchet cap, none added)
  • Serialization-drift canary (RehydrateEntity_Roundtrip_EveryPropertyNonNull) covers the new field automatically (reflection-filled); MCP fields schema is reflection-generated — no manual sync needed

Post-merge

  • Backend deploy, then grant the optional Graph feature W365CloudPcValidation (CloudPC.Read.All) in the test tenant and enable the tenant flag before field test 2
  • No MCP redeploy needed (only a comment touched in sessions.ts; search_docs corpus is fed by the customer docs repo, which is untouched)

🤖 Generated with Claude Code

claude and others added 2 commits August 6, 2026 21:01
Windows 365 Cloud PCs are never Autopilot-registered, so the serial lookup
(and the Corporate Identifier fallback) structurally reject them and Cloud PC
first-connect enrollment could not be monitored. New stage-3 fallback in the
device-validation chain: extract the Intune device id from the chain-validated
MDM client certificate's Subject CN (field-verified: CN == managedDevice id ==
cloudPC.managedDeviceId) and require a matching cloudPC object in the claimed
tenant (Graph v1.0 virtualEndpoint/cloudPCs, $filter=managedDeviceId eq).
Only service-provisioned Cloud PCs have such an object — a regular enrolled
non-Autopilot device still ends in the 403, and local W365 markers are never
consulted for auth.

- Shared: ValidatorType.CloudPc = 5, TenantConfiguration.ValidateCloudPcDevice
  (default false, tenant-admin writable), GraphAppPermissions.CloudPCReadAll,
  GraphFeatureCatalog feature W365CloudPcValidation -> CloudPC.Read.All
- Backend: CloudPcDeviceValidator (AutopilotDeviceValidator sibling contract:
  30/5 min cache, 2 attempts, transient->503; GUID gate before the OData
  filter; exact-match guard; Graph 403 = definitive "grant missing", not
  transient), SecurityValidator CN extraction + fallback stage + hard gate,
  wiring through the 5 agent endpoints, TableConfigRepository persistence
- Consent script: W365CloudPcValidation in catalog/ValidateSet/help; NEW
  GraphFeatureCatalogSyncTests pins C# catalog <-> grant script (previously
  comment-only lock-step)
- Bootstrap dev script v2.3-dev.3: guard 4 (uptime window) exempted while the
  Cloud-PC relax is active — Cloud PCs run headless for days before first
  connect; the fresh-profile window is the time anchor there (+ Pester tests)
- Web: validation toggle + context plumbing, ValidatedBy "CloudPc" label,
  privacy-page validation claim generalized (Last updated bumped)
- Docs: OKF concept doc backend/cloudpc-device-validation.md + index + log

Verified: web tsc clean, ESLint 0 errors (99 pre-existing warnings = ratchet
cap, none added). .NET build/tests and Pester run in CI — SDK download and
pwsh are unavailable in this sandbox's network policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RqEFrcAHhWgCvgJgHGzMuv
…hase"

Companion to the CloudPc validator: the agent now classifies Windows 365
Cloud PCs locally with the bootstrap's field-verified marker AND
(HKLM\SOFTWARE\Microsoft\Windows365 key + installed
CloudManagedDesktopExtension service, probed via its Services registry key
so net48 needs no System.ServiceProcess). SKIP-safe: any error resolves
false. Never an auth input - backend admission stays exclusively the
cert-CN-bound Graph lookup (ValidatedBy=CloudPc); the two signals are
independent and may disagree.

Two transport rails:

- Session metadata: SessionRegistration.IsCloudPc -> Sessions table
  (sticky-true OR across re-registrations, IsSelfDeployingProfile pattern)
  -> SessionsIndex full mirror -> SessionSummary -> new isCloudPc search
  filter end to end (SearchSessions + QueryRawSessions + pagination
  fingerprint + all three in-memory predicates; MCP search_sessions /
  query_raw_sessions params + summary projection; web type, Cloud PC pill,
  SessionInfoCard enrollment label, CSV export appended column).
- Decision engine: isCloudPc rides the EnrollmentFactsObserved payload,
  recorded set-once for BOTH values in EnrollmentScenarioObservations.CloudPc
  and annotates the scenario profile reason
  cloud_pc_first_connect:no_device_esp_expected - the engine-recorded
  expectation that Device-ESP already ran headless at provisioning and the
  session starts at Account Setup. Deliberately reason+observation only:
  no completion arm depends on DeviceSetup, Mode stays AccountSetup/IME-
  classified (Classic), EspConfig keeps configured-FirstSync semantics.
  Positive marker surfaces as cloud_pc_marker in the audit-trail census.
  Updater idempotency: the cloudPcAnnotates gate keeps the generic v1
  reason branch from re-claiming the slot on repeat posts (would bump
  EvidenceOrdinal every post). Additive-nullable observation - no state
  schema bump (v4 convention), serializer roundtrip pinned.

Tests: CloudPcDetector resolver (marker AND, throwing probe), registration
body, facts-signal payload shape, updater annotation/idempotency/
self-deploying interplay, reducer observation both values + census fold,
serializer roundtrip, index projection superset, sticky-true both
directions. Suites: DecisionCore 500, Functions 3941, Agent V2 2154,
vitest 632 - all green; tsc web+MCP clean; ESLint 0 errors (cap 99).

Docs: cloudpc-device-validation.md follow-up section replaced with the
shipped mechanism + log entry.

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

Copy link
Copy Markdown
Owner Author

Addendum (0d8e7dc9) — agent-side IsCloudPc flag + decision-engine expectation "no Device-ESP phase" (the follow-up the original commit listed as out of scope now ships in this PR):

  • Agent: new CloudPcDetector — bootstrap-verified marker AND (Windows365 registry key + installed CloudManagedDesktopExtension service, probed via its Services registry key; no System.ServiceProcess dependency), SKIP-safe. Never an auth input — independent of the server-derived ValidatedBy=CloudPc.
  • Session rail: SessionRegistration.IsCloudPc → Sessions table (sticky-true OR) → SessionsIndex full mirror → SessionSummary → isCloudPc filter end to end (search + raw + pagination fingerprint + MCP params/projection + web pill/label/CSV).
  • Engine rail: isCloudPc on the EnrollmentFactsObserved payload → EnrollmentScenarioObservations.CloudPc (set-once, both values) + profile reason cloud_pc_first_connect:no_device_esp_expected + cloud_pc_marker in the audit-trail census. Reason+observation only: no completion arm depends on DeviceSetup, Mode stays AccountSetup/IME-classified, EspConfig keeps configured-FirstSync semantics. Additive-nullable — no state schema bump.
  • Verified: DecisionCore 500, Functions 3941, Agent V2 2154, SummaryDialog 44, vitest 632 — all green; tsc web+MCP clean; ESLint 0 errors (cap 99 unchanged). 16 new tests incl. updater idempotency (repeat facts post must not bump EvidenceOrdinal) and sticky-true both directions.

🤖 Generated with Claude Code

@okieselbach
okieselbach merged commit 1c5f69a into main Aug 7, 2026
10 checks passed
@okieselbach
okieselbach deleted the claude/w365-autopilot-validation-rolg8s branch September 4, 2026 21:07
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.

2 participants