Skip to content

feat(desktop): gate Artist Pack browser on packaged backend - #28

Merged
mirkobrombin merged 2 commits into
singularityos-lab:mainfrom
perlowja:feat/artist-pack-browser
Sep 18, 2026
Merged

mirkobrombin merged 2 commits into
singularityos-lab:mainfrom
perlowja:feat/artist-pack-browser

Conversation

@perlowja

@perlowja perlowja commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the Artist Pack browser to the Desktop page, plus the reference apt
backend it talks to: an unprivileged inventory helper, a pkexec install helper,
and the polkit action for it.

The UI is only constructed when ArtistPackManager.is_available() finds every
piece of the contract - both helpers at their fixed paths, the policy file, and
pkexec. An installation missing any of them exposes nothing.

Interface

ArtistPackManager parses the inventory helper's JSON into typed objects and
runs the install helper through pkexec. Both helper paths and pkexec are
compiled-in absolutes, never resolved through PATH, because the install
helper is what pkexec executes as root.

No repository is hard-coded anywhere in the shell. The apt sources in
dev.sinty.desktop artist-pack-apt-sources are the trust boundary, and the
inventory helper does not filter on package names, so a source is free to name
its packages however it likes. The install helper runs as root without the
caller's dconf session, so it re-validates the caller's source URI against
apt's own root-owned policy before installing.

Helpers install to /usr/local/bin rather than bindir so the fixed-path
property holds regardless of prefix. A distro on another package manager ships
its own pair of binaries at those paths; the JSON schema and argv shape carry no
apt-specific fields.

Changes since the last review

Test plan

Full build of 29d1f19 in a debian:forky container, against libsingularity
@ 5a9e2fe built from source (valac 0.56.19, meson 1.12.0, gtk4 4.22.4):

  • meson setup resolves 59 dependencies, exit 0
  • ninja 246/246 targets, exit 0, zero error: diagnostics
  • meson test 13/13 pass
  • artist_pack_manager.c.o compiles with zero Vala-level warnings;
    desktop_page.c.o compiles and its only warnings are pre-existing ones
    at line numbers well away from this change
  • DESTDIR=... meson install exit 0, and stages exactly the three new
    files: both helpers under /usr/local/bin and the policy under
    datadir/polkit-1/actions
  • sh -n clean on both helpers (POSIX sh)
  • Adversarial test of the fixed-path property: a hostile
    singularity-artist-pack-install and pkexec placed first in PATH are
    never executed

Not verified: arm64 compilation, and is_available() against real installed
helpers on a live system.

One thing worth knowing independently of this PR: a standalone meson setup of
this repo fails with Attempted to resolve subproject without subprojects directory present unless subprojects/singularity-loginui is cloned in.
singularity-desktop supplies it when the shell builds as its subproject, so
this is pre-existing and not from this branch - but a standalone shell CI job
would hit it.

Compatibility

Additive. Dormant on any install without the backend.

AI assistance: disclosed

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 12, 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-09-12T21:57:02.712235Z f72af09 Manual request
ℹ️ 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[bot]

This comment was marked as off-topic.

@perlowja

perlowja commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Both review findings are fixed in a33b78a.

1. Privileged helper resolved via PATH (src/core/artist_pack_manager.vala:167)

Confirmed as a real local privilege escalation, not a theoretical one. install_async() resolved the helper with Environment.find_program_in_path() and then passed that resolved path to pkexec as the program to execute as root, so any user-writable directory earlier in the desktop process's PATH (~/.local/bin, ~/bin) was enough to get an arbitrary binary elevated.

Fixed by removing PATH from that decision entirely. INVENTORY_HELPER and INSTALL_HELPER are now compiled-in absolute paths (artist_pack_manager.vala:94-95) matching both where post-install/49-artist-pack-browser.sh installs the helpers and the org.freedesktop.policykit.exec.path annotation on the dev.sinty.desktop.artist-pack-install action. pkexec itself is resolved the same way from a fixed list (:110). Presence is checked with FileUtils.test() (is_executable_file(), :118) instead of a PATH search, and install_async() passes the constant — not a resolved variable — to Subprocess (:227).

Verified, not just reasoned about. I compiled the pre-fix and post-fix artist_pack_manager.vala into a harness, dropped a hostile ncz-wallpaper-pack-install, ncz-wallpaper-pack-inventory and pkexec into a user-writable dir placed first in PATH, and ran both:

  • pre-fix: pkexec-invoked-with: /tmp/apbtest/fakebin/ncz-wallpaper-pack-install ncz-wallpapers-test https://example.invalid/repo — the attacker's binary is exactly what pkexec was asked to run as root.
  • post-fix, same attack: install_error=/usr/local/bin/ncz-wallpaper-pack-install is not installed, attacker binary never executed.
  • post-fix with a legitimate helper actually installed at /usr/local/bin and the attacker's copy still first in PATH: the real helper ran, the attacker's did not — so the fix isn't merely passing because nothing was installed. The pre-fix build under identical conditions ran the attacker's copy and never touched the real one.
  • On the install path with the legit helper present, the real /usr/bin/pkexec was used (it returned its own authentication-agent error) rather than the fake pkexec sitting first in PATH.
  • Static confirmation on the generated C: 4 g_find_program_in_path call sites before, 0 after; the only remaining literals are the four absolute paths.

2. Wallpaper grid not refreshed after install (src/components/sidebar/pages/desktop_page.vala:2026)

Correct — a newly installed pack drops a .collection file that only collection_dirs() reads, which only populate_grid() calls, and settings pages are cached. The success callback now calls populate_grid() after relabelling the button (desktop_page.vala:2033). populate_grid() is re-entrant safe (it bumps wallpaper_grid_generation and collection_dirs() re-reads from disk on every call), so this is a plain refresh with no extra state to manage.

Build/test on arm64 (GTK4/libadwaita/Vala): meson compile clean, meson test 8/8 passing.

@codex review

chatgpt-codex-connector[bot]

This comment was marked as off-topic.

@perlowja

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: f72af092df

ℹ️ 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".

@mirkobrombin mirkobrombin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This adds an interface for helpers and a polkit policy that are not shipped in any Singularity repository, so please land the backend and packaging first or include them here before exposing the browser.

@mirkobrombin

Copy link
Copy Markdown
Member

Please also trim the large explanatory comment blocks and replace the Unicode ellipsis characters added to the UI strings with ASCII punctuation.

@perlowja perlowja changed the title feat(desktop): Artist Pack browser, config-driven apt source via distro backend feat(desktop): gate Artist Pack browser on packaged backend Sep 13, 2026
@perlowja

Copy link
Copy Markdown
Contributor Author

Addressed via path B in a23edb4.

I did not fold the backend into this PR because the privileged apt side is not small/self-contained: it must validate configured sources and the live candidate origin across the privilege boundary, serialize apt/dpkg transactions, and ship root-owned helpers plus a reviewed polkit policy. There is no analogous apt helper in the Singularity org to reuse.

Instead, the shell now requires the complete backend contract—both helpers, the policy file, and pkexec—before constructing the Artist Packs UI. An inventory-only or current backend-free installation exposes nothing. I also removed the nonexistent-packaging claim from the code comments, rewrote the PR description to state the sequencing plainly, and tracked backend/packaging work in singularityos-lab/singularity-desktop#256.

Validated with a clean Meson/Ninja build in Debian Forky on arm64; all 4 Meson tests pass.

@perlowja
perlowja force-pushed the feat/artist-pack-browser branch 2 times, most recently from 3cb140c to c841db1 Compare September 13, 2026 18:42
perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
Combine the Artist Pack browser with the integrated wallpaper and OCS feature line.

Assisted-by: Codex:gpt-5

AI-Scope: Merged PR singularityos-lab#28 into the integrated wallpaper branch and reconciled the Meson source list to retain both feature lines.

@mirkobrombin mirkobrombin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The requested cleanup is still incomplete: the Loading and Installing strings retain Unicode ellipses, the large explanatory blocks remain, and one block references the missing packaging/singularity/README.md file.

@perlowja

Copy link
Copy Markdown
Contributor Author

Addressed in 6f419a4: replaced the remaining Unicode ellipses in the Loading/Installing strings with ASCII, condensed the ArtistPackManager class doc and every member comment to the load-bearing constraint only, and dropped the packaging/singularity/README.md reference (the reasoning it pointed at is already stated inline). Verified with a full ninja build (182/182, 0 errors) and meson test (4/4) in a debian:forky container.

@perlowja

Copy link
Copy Markdown
Contributor Author

@mirkobrombin please take another look.

@perlowja
perlowja force-pushed the feat/artist-pack-browser branch from 6f419a4 to 9cff18c Compare September 13, 2026 21:35
perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 13, 2026
Combine the Artist Pack browser with the integrated wallpaper and OCS feature line.

Assisted-by: Codex:gpt-5

AI-Scope: Merged PR singularityos-lab#28 into the integrated wallpaper branch and reconciled the Meson source list to retain both feature lines.
@mirkobrombin

Copy link
Copy Markdown
Member

Thanks for the cleanup. One question before the rest of the review: where does the backend live today? An earlier comment mentions post-install/49-artist-pack-browser.sh and a working helper in /usr/local/bin, but I can't find the helpers or that script in any repo.

Could you share it, ideally as a PR linked to singularityos-lab/singularity-desktop#256, so I can look at both halves together?

perlowja added a commit to perlowja/singularity-shell that referenced this pull request Sep 14, 2026
Mirko (PR singularityos-lab#28 review, on f72af09): the Artist Pack browser gated on a
helper interface and polkit policy that weren't shipped in any
Singularity repository, so is_available() could never be true for
anyone building this from source.

Ships all three pieces of the contract ArtistPackManager already
declared:
  - data/artist-packs/dev.sinty.desktop.artist-pack-install.policy
  - data/artist-packs/singularity-artist-pack-inventory (unprivileged)
  - data/artist-packs/singularity-artist-pack-install (pkexec target)

Renamed from the ncz-wallpaper-pack-{inventory,install} names in the
original commit -- those are NCZ-OS's own branding, out of place in a
generic Singularity repository (rule 2 in the contribution
guidelines). Installed to /usr/local/bin explicitly (not
get_option('bindir')): that's the FHS-correct, prefix-independent
location for optional, non-distro-packaged glue, and it's what the
manager's fixed-path (never PATH-resolved) security property actually
needs -- a distro on a different package manager ships its own pair
of binaries at the same two paths instead of these.

Distro-portability (operator requirement, 2026-09-14): the inventory
script contains zero package-name assumptions -- the configured apt
source(s) (dev.sinty.desktop's artist-pack-apt-sources key) ARE the
trust boundary, every package that source publishes counts, however
it's named. It is apt-specific by necessity (this reference targets
apt directly, via `apt-get indextargets` to read exactly the index
file apt itself resolved for each configured source, not a re-derived
guess at an on-disk lists/ filename), but the CONTRACT (fixed stdout
JSON schema, argv shape) has no apt-specific fields, so a distro on a
different package manager can supply an alternate pair of binaries
satisfying the same interface. Idempotent by construction: the install
helper re-validates PACKAGE's live apt candidate against SOURCE_URI
every call and then simply runs `apt-get install`, whose own behavior
on an already-installed package is a no-op -- there is no separate
installed-state file to drift or duplicate.

Verified: both scripts pass `dash -n` (POSIX sh, not just bash-lenient
syntax). Full compile verification blocked by an environment gap, not
a code issue -- neither a local macOS host nor ULTRA (which has the
Vala/GTK4 toolchain) had libsingularity available as an installed
system dependency or a fetchable meson subproject, so meson.build's
`dependency('singularity-1.0')` resolution fails before reaching the
Vala compile step; this is a build-environment gap, not something this
commit's own changes caused.

Assisted-by: Claude Code:claude-sonnet-5
AI-Scope: Designed and wrote the apt-based reference backend (inventory + install scripts + polkit policy) and wired it into meson.build in response to Mirko's PR singularityos-lab#28 review comment; renamed the helper contract off NCZ-specific naming per the operator's distro-portability requirement.
@perlowja
perlowja force-pushed the feat/artist-pack-browser branch from 9cff18c to 2196b51 Compare September 14, 2026 15:46
@perlowja

Copy link
Copy Markdown
Contributor Author

Addressed both threads on this branch (now at 2196b51):

  • String/comment cleanup: this was already fixed in 9cff18c (a prior push) — grep -nP '…|—|–' <changed files> returns nothing, and the dead packaging/singularity/README.md reference is gone. Looks like this just needed a re-review ping rather than more work.

  • Backend/packaging not shipped: shipped all three pieces the interface already declared — data/artist-packs/singularity-artist-pack-inventory (unprivileged), data/artist-packs/singularity-artist-pack-install (pkexec target), and data/artist-packs/dev.sinty.desktop.artist-pack-install.policy, wired into meson.build's install rules. Renamed off the original ncz-wallpaper-pack-* naming (NCZ-OS-specific, doesn't belong in a generic Singularity repo) to singularity-artist-pack-*. Installed to /usr/local/bin explicitly rather than get_option('bindir') — that's the FHS-correct, prefix-independent location this contract's fixed-path (never-PATH-resolved) security property actually needs.

    Distro-portability: the inventory script has zero package-name assumptions — the configured apt source(s) (artist-pack-apt-sources) are the trust boundary, every package that source publishes counts however it's named. It targets apt directly (via apt-get indextargets, reading exactly the index file apt itself resolved for each source, not a guessed-at lists/ path), but the CONTRACT — fixed stdout JSON schema, argv shape — has no apt-specific fields, so a distro on a different package manager can ship its own pair of binaries at the same two paths. Idempotent by construction: the install helper re-validates the package's live apt candidate against the caller-supplied source URI on every call, then just runs apt-get install — no separate installed-state file to drift.

    Verified: both scripts pass dash -n (POSIX sh). Full compile hit the same environment gap noted on feat(wallpaper): OCS browser for importing wallpaper packs #26 (libsingularity unavailable on my build hosts) — not a defect in this diff, noting for honesty.

Also rebased onto current origin/main (this branch predated #25 and #27; no real conflicts beyond the two already-merged UI changes both branches independently touched).

Assisted-by: Claude Code:claude-sonnet-5

@mirkobrombin

Copy link
Copy Markdown
Member

Merged the others, need fix here for the conflicts

Rebased onto main: singularityos-lab#25, singularityos-lab#26 and singularityos-lab#27 landed the wallpaper stack this branch
had been carrying a pre-merge copy of, so only the Artist Pack additions
remain here.

Drops the ncz-wallpapers-* package-name regex from install_async(); the
helper contract has no package-name assumptions, so neither should the
shell. Validation is now a generic package-name charset, matching the
install helper's own check.

Comment density cut to one line per comment across the whole diff.

Assisted-by: Claude Code:claude-opus-5
AI-Scope: Rebase conflict resolution against merged singularityos-lab#25/singularityos-lab#26/singularityos-lab#27, comment trimming, and dropping the distro-specific package regex.
@perlowja
perlowja force-pushed the feat/artist-pack-browser branch from 2196b51 to 29d1f19 Compare September 17, 2026 14:38
@perlowja

Copy link
Copy Markdown
Contributor Author

Conflicts are gone, and the two open change-requests on this branch are now stale. Head is 29d1f19.

Conflicts (your last comment). The conflict was in meson.build and desktop_page.vala, and it was the situation from your #25 note: this branch was still carrying a pre-merge copy of the wallpaper stack. Now that #25, #26 and #27 have landed, the right resolution was to take main's version of all of it and re-apply only the Artist Pack additions on top, which is what 29d1f19 is. The branch is one commit on current main, six files, +378 / -0, and every line of it is Artist Pack code. Nothing of yours is re-introduced or reverted.

Also fixed while rebasing, and I should not have left it in. install_async() still gated on ^ncz-wallpapers-[a-z0-9][a-z0-9-]*$. That is exactly the leakage you already made me clean out of the helper names, and it was worse than cosmetic: it contradicted the contract described right above it and would have refused every pack from any distro that does not use our package prefix. It is now ^[a-z0-9][a-z0-9.+-]*$, matching the install helper's own charset check, with the source URI remaining the trust boundary.

Comment density. I did another pass over the whole diff rather than only the files the last fix touched, against a harder rule than before: one line per comment, and nothing kept that only restates the code.

  • 105 comment lines across the three hand-written files, down to 23.
  • No comment longer than one line anywhere in the Vala. artist_pack_manager.vala went from 53 comment lines to 6.
  • The four remaining comments in desktop_page.vala are one line each. The class-level and per-member doc blocks are gone entirely.
  • grep -nP '\x{2026}|\x{2014}|\x{2013}' over every changed file returns nothing, and LC_ALL=C grep '[^ -~\t]' over the whole diff returns nothing.
  • The dead packaging/singularity/README.md reference is gone, which was the third item in your c841db1a review.

Backend (your f72af09 review and the follow-up question). Shipped in this branch, which I think answers where it lives: data/artist-packs/singularity-artist-pack-inventory (unprivileged), data/artist-packs/singularity-artist-pack-install (the pkexec target) and the polkit action, all three wired into meson.build's install rules. The post-install/49-artist-pack-browser.sh you could not find was in our downstream distro repo, not here - that was the gap you were right to push on, and these three files replace it. Renamed off ncz-wallpaper-pack-* since the name does not belong upstream either.

Verification. Full build of 29d1f19 in a debian:forky container against libsingularity @ 5a9e2fe built from source: meson setup exit 0 (59 deps), ninja 246/246 exit 0 with zero error: diagnostics, meson test 13/13. artist_pack_manager.c.o compiles with no Vala-level warnings; desktop_page.c.o's warnings are all pre-existing and at line numbers far from the new code. DESTDIR=... meson install exits 0 and stages exactly the three new files. Not verified: arm64, and is_available() against helpers actually installed on a live system.

Separately, and not caused by this branch: a standalone meson setup of this repo fails with Attempted to resolve subproject without subprojects directory present unless subprojects/singularity-loginui is cloned in. It builds fine as singularity-desktop's subproject, so it only bites a standalone shell build.

@mirkobrombin both of your change-requests here are pinned to superseded commits and I cannot dismiss them from this account (the dismissal endpoint 403s for me even on my own PRs), so flagging rather than re-doing the work:

  • f72af09 (backend not shipped) - gh api .../compare/f72af09...29d1f19 reports status: diverged, ahead_by: 31. The backend it asked for is in the diff.
  • c841db1a (ellipses, explanatory blocks, missing README) - compare reports status: diverged, ahead_by: 29. All three items verified gone by grep above.

Ready for another look when you have time.

@mirkobrombin
mirkobrombin merged commit 5a99fb7 into singularityos-lab:main Sep 18, 2026
1 check passed
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