An xbox launch kind a de-privileged plugin can publish #75

Merged
enricobuehler merged 1 commits from worktree-xbox-launch-kind into main 2026-08-06 14:48:08 +00:00
Owner

Follow-up to #70, and the thing that unblocks an extracted xbox library plugin.

The measurement

The xbox scanner resolves each package's PackageFamilyName by enumerating %ProgramData%\Microsoft\Windows\AppRepository\Packages. Probed on .173 with a temp scheduled task (design doc S1, now answered):

principal result
NT AUTHORITY\LocalServicethe plugin runner UnauthorizedAccessException
LocalSystemthe host service count=348

The 348-vs-denied contrast is what makes that a permission verdict rather than an empty directory. Everything else the Windows plugins need is readable de-privileged: C:\XboxGames, Epic's %ProgramData% manifests, and the HKLM Steam root.

So an extracted xbox plugin cannot build an AUMID, and aumid — which takes a complete <PFN>!<AppId> — is unpublishable by one.

The fix, which is neither fallback the design doc offered

The doc proposed either entries without aumid (they would list but not launch), or a one-shot elevated PFN resolve at install time. This is a third option the probe made obvious:

The plugin sends <Identity>!<AppId> — both read straight out of MicrosoftGame.config, which is readable de-privileged — and the host resolves the publisher hash at launch time, where it already owns command construction.

  • D1 holds: the plugin supplies a validated value, the host builds the command.
  • Same shape as the playnite kind from #70.
  • Resolving late means a package update that changes the publisher hash cannot leave a stale unlaunchable tile behind — which the install-time cache would have.

xbox_pfn therefore moves from the scan path to the launch path rather than being deleted, and its doc comment records the privilege asymmetry so the next reader doesn't have to re-derive it.

The charset guard is now load-bearing

Factored out as aumid_part / valid_aumid and shared with the aumid kind. On aumid it was belt-and-braces — every value was host-derived. On xbox the Identity arrives over the wire from a plugin and is interpolated into a shell:AppsFolder\… argument, so it is validated at reconcile too: a malformed value is a 400 the plugin author can act on, not a tile that fails on click.

Gates

Gate Where Result
cargo test -p punktfunk-host .21 438 passed / 0 failed
cargo test -p punktfunk-host --bin punktfunk-host library:: .173 55 passed / 0 failed

Both +1 for the new guard test, and .173 is where these arms actually compile — Linux CI never sees them.

The plugin that uses it

unom/punktfunk-plugin-xbox, branch feat/xbox-library-source (7 tests, tsc + biome clean). That repo was empty, so it has no main branch yet and no PR can target it.

Still owed there: parity --compare on a Windows box with GDK titles installed, under the runner.

Follow-up to #70, and the thing that unblocks an extracted xbox library plugin. ## The measurement The xbox scanner resolves each package's PackageFamilyName by enumerating `%ProgramData%\Microsoft\Windows\AppRepository\Packages`. Probed on `.173` with a temp scheduled task (design doc **S1**, now answered): | principal | result | |---|---| | `NT AUTHORITY\LocalService` — **the plugin runner** | `UnauthorizedAccessException` | | `LocalSystem` — **the host service** | `count=348` | The 348-vs-denied contrast is what makes that a permission verdict rather than an empty directory. Everything *else* the Windows plugins need is readable de-privileged: `C:\XboxGames`, Epic's `%ProgramData%` manifests, and the HKLM Steam root. So an extracted xbox plugin cannot build an AUMID, and `aumid` — which takes a complete `<PFN>!<AppId>` — is unpublishable by one. ## The fix, which is neither fallback the design doc offered The doc proposed either entries without `aumid` (they would list but not launch), or a one-shot elevated PFN resolve at install time. This is a third option the probe made obvious: **The plugin sends `<Identity>!<AppId>`** — both read straight out of `MicrosoftGame.config`, which *is* readable de-privileged — **and the host resolves the publisher hash at launch time**, where it already owns command construction. - D1 holds: the plugin supplies a validated value, the host builds the command. - Same shape as the `playnite` kind from #70. - Resolving *late* means a package update that changes the publisher hash cannot leave a stale unlaunchable tile behind — which the install-time cache would have. `xbox_pfn` therefore moves from the scan path to the launch path rather than being deleted, and its doc comment records the privilege asymmetry so the next reader doesn't have to re-derive it. ## The charset guard is now load-bearing Factored out as `aumid_part` / `valid_aumid` and shared with the `aumid` kind. On `aumid` it was belt-and-braces — every value was host-derived. On `xbox` the Identity **arrives over the wire from a plugin** and is interpolated into a `shell:AppsFolder\…` argument, so it is validated at reconcile too: a malformed value is a 400 the plugin author can act on, not a tile that fails on click. ## Gates | Gate | Where | Result | |---|---|---| | `cargo test -p punktfunk-host` | `.21` | **438 passed / 0 failed** | | `cargo test -p punktfunk-host --bin punktfunk-host library::` | `.173` | **55 passed / 0 failed** | Both +1 for the new guard test, and `.173` is where these arms actually compile — Linux CI never sees them. ## The plugin that uses it `unom/punktfunk-plugin-xbox`, branch `feat/xbox-library-source` (7 tests, tsc + biome clean). That repo was empty, so it has no `main` branch yet and no PR can target it. Still owed there: `parity --compare` on a Windows box with GDK titles installed, under the runner.
enricobuehler added 1 commit 2026-08-06 13:43:57 +00:00
feat(host/library): an xbox launch kind a de-privileged plugin can publish
apple / swift (pull_request) Successful in 1m27s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 1m22s
android / android (pull_request) Successful in 3m9s
ci / docs-site (pull_request) Successful in 1m9s
ci / web (pull_request) Successful in 3m20s
ci / rust (pull_request) Failing after 9m9s
166e158afe
The xbox scanner resolves each package's PackageFamilyName by enumerating
%ProgramData%\Microsoft\Windows\AppRepository\Packages. Probed on .173 (design doc S1),
that directory is:

  NT AUTHORITY\LocalService   UnauthorizedAccessException   <- the plugin runner
  LocalSystem                 count=348                     <- the host service

So an extracted xbox plugin cannot build an AUMID, and `aumid` — which takes a complete
`<PFN>!<AppId>` — is unpublishable by one.

The design doc offered two fallbacks: entries without `aumid` (they would list but not
launch), or a one-shot elevated PFN resolve at install time. This is neither. The plugin
sends `<Identity>!<AppId>`, both read straight out of MicrosoftGame.config — which IS
readable de-privileged — and the host resolves the publisher hash at LAUNCH time, where it
already owns command construction.

That keeps D1 intact (the plugin supplies a validated value, the host builds the command),
it is the same shape as the `playnite` kind, and resolving late means a package update that
changes the publisher hash cannot leave a stale unlaunchable tile behind — which the
install-time cache would have.

`xbox_pfn` therefore moves from the scan path to the launch path rather than being deleted,
and its doc records the privilege asymmetry so the next reader doesn't re-derive it.

The charset guard is factored out (`aumid_part` / `valid_aumid`) and shared with the `aumid`
kind. On `aumid` it was belt-and-braces — every value was host-derived. On `xbox` it is
load-bearing: the Identity arrives over the wire from a plugin, and it is interpolated into
a `shell:AppsFolder\…` argument. Validated inbound at reconcile too, so a malformed value is
a 400 the plugin author can act on rather than a tile that fails on click.

Gates: punktfunk-host 438 passed / 0 failed on .21, and 55/0 for the library tests on .173
(where these arms actually compile). Both counts +1 for the new guard test.
enricobuehler merged commit eb4b33ca49 into main 2026-08-06 14:48:08 +00:00
enricobuehler deleted branch worktree-xbox-launch-kind 2026-08-06 14:48:09 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#75