Revoke stale plugin facade capabilities

Reported-by: Roger Piñol <rogerpicar@gmail.com>
This commit is contained in:
acrogenesis committed 2026-09-02 12:11:15 -06:00
1 parent 094c913065
commit 203e1639c3
3 files changed
+266 -18

No files matched your search

+16
View File
@@ -95,6 +95,22 @@ qml_matches "$shell_qml" 'shell\.prunePluginApis\( *\)' ||
fail "disabled plugin facade caches are pruned"
pass "plugin facade synchronization is bounded"
qml_matches "$shell_qml" 'descriptor\.profile *!== *expectedProfile[^}]*shell\.revokePluginShellApi\( *shellKey *\)' ||
fail "manifest capability changes do not revoke cached plugin facades"
qml_matches "$shell_qml" 'shell\.barPluginMayControl\( *currentManifest\( *\), *requestedId *\)' ||
fail "bar lifecycle callbacks do not validate the current manifest"
qml_matches "$shell_qml" 'return hasCurrentBarCapabilities\( *\) *\? *shell\.mutatePluginBarConfig\( *mutator *\) *: *false' ||
fail "bar configuration mutation does not validate the current manifest"
pass "manifest changes revoke cached facade capabilities"
qml_matches "$shell_qml" 'shell\.serviceFor\( *shell\.pluginRegistry\.resolveEnabledId\( *id *\) *\)' ||
fail "narrow first-party service proxies do not resolve enabled clones"
qml_matches "$shell_qml" 'return serviceFor\( *shell\.pluginRegistry\.resolveEnabledId\( *pluginId *\) *\)' ||
fail "trusted first-party service lookups do not resolve enabled clones"
qml_matches "$shell_qml" 'allowOwnService *&& *shell\.pluginOwnsTarget\( *key, *requestedId *\)[^}]*return shell\.pluginServiceFor\( *key, *requestedId *\)' ||
fail "cloned widgets cannot use a source id to reach their own service"
pass "service facades resolve enabled clones without widening replacement-bar access"
require_compositor "plugin authentication boundary runtime test"
if ! command -v quickshell >/dev/null 2>&1; then
+155 -2
View File
@@ -136,6 +136,66 @@ Item {
}
QML
# A clone of the built-in media service exercises both supported service paths:
# its own widget receives the raw companion service under the trusted bar, while
# a replacement bar receives only the narrow media proxy resolved to the clone.
media_clone_id="acme.media-clone"
media_clone_dir="$test_home/.config/omarchy/plugins/$media_clone_id"
mkdir -p "$media_clone_dir"
cat >"$media_clone_dir/manifest.json" <<JSON
{
"schemaVersion": 1,
"id": "$media_clone_id",
"name": "Media Clone",
"version": "1.0.0",
"kinds": ["service", "bar-widget"],
"entryPoints": {"service": "Service.qml", "barWidget": "BarWidget.qml"},
"barWidget": {"defaultSection": "center"},
"omarchy": {"clonedFrom": "omarchy.media"}
}
JSON
cat >"$media_clone_dir/Service.qml" <<'QML'
import QtQuick
import Quickshell.Io
Item {
property string marker: "clone-service"
property bool enabled: true
property var activePlayer: null
property var sourcePlayers: []
function runAction(action, showFeedback, targetKey) {}
function playerKey(player) { return "" }
function selectPlayer(playerKey) {}
IpcHandler {
target: "acme-media-clone-service"
function ping(): string { return marker }
}
}
QML
cat >"$media_clone_dir/BarWidget.qml" <<'QML'
import QtQuick
import Quickshell.Io
Item {
id: root
property var bar: null
IpcHandler {
target: "acme-media-clone-widget"
function probeOwnService(): string {
var service = root.bar && root.bar.shell
? root.bar.shell.firstPartyServiceFor("omarchy.media") : null
return JSON.stringify({
reachable: !!service,
marker: service ? String(service.marker || "") : ""
})
}
}
}
QML
review_bar_id="acme.review-bar"
review_bar_dir="$test_home/.config/omarchy/plugins/$review_bar_id"
mkdir -p "$review_bar_dir"
@@ -145,8 +205,9 @@ cat >"$review_bar_dir/manifest.json" <<JSON
"id": "$review_bar_id",
"name": "Review Bar",
"version": "1.0.0",
"kinds": ["bar"],
"entryPoints": {"bar": "Bar.qml"}
"kinds": ["bar", "service"],
"keepLoaded": true,
"entryPoints": {"bar": "Bar.qml", "service": "Service.qml"}
}
JSON
cat >"$review_bar_dir/Bar.qml" <<'QML'
@@ -181,6 +242,12 @@ Item {
return JSON.stringify(root.barConfig || {})
}
function probeMediaProxy(): string {
var service = root.shell
? root.shell.firstPartyServiceFor("omarchy.media") : null
return JSON.stringify({ reachable: !!service, enabled: service ? service.enabled === true : false })
}
function mutateSnapshot(): string {
if (root.barConfig && root.barConfig.layout
&& root.barConfig.layout.left && root.barConfig.layout.left.length > 0)
@@ -190,6 +257,38 @@ Item {
}
}
QML
cat >"$review_bar_dir/Service.qml" <<'QML'
import QtQuick
import Quickshell.Io
Item {
id: root
property var shell: null
property var retainedShell: null
onShellChanged: if (!retainedShell && shell) retainedShell = shell
function mutationAllowed(candidate) {
if (!candidate) return false
try {
return typeof candidate.mutateShellConfig === "function"
&& candidate.mutateShellConfig(function(config) {}) === true
} catch (e) {
return false
}
}
IpcHandler {
target: "acme-review-capability"
function probe(): string {
return JSON.stringify({
currentAllowed: root.mutationAllowed(root.shell),
retainedAllowed: root.mutationAllowed(root.retainedShell)
})
}
}
}
QML
cat >"$stub_bin/omarchy-update-available" <<'SH'
#!/bin/bash
@@ -517,6 +616,24 @@ pass "bar put leaves a widget already on the bar alone"
# Run the replacement-bar probes last: switching bar loaders can transiently
# leave bar-aware panels without a visual host, which should not add noise to
# the default-bar assertions above.
[[ $(shell_ipc shell setPluginEnabled "$media_clone_id" true) == "ok" ]] ||
fail_with_log "media clone fixture could not be enabled"
clone_widget_probe=""
for _ in {1..80}; do
clone_widget_probe=$(shell_ipc acme-media-clone-widget probeOwnService 2>/dev/null || true)
if jq -e '.reachable == true and .marker == "clone-service"' \
<<<"$clone_widget_probe" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
jq -e '.reachable == true and .marker == "clone-service"' \
<<<"$clone_widget_probe" >/dev/null || {
printf 'Clone own-service probe: %s\n' "$clone_widget_probe" >&2
fail_with_log "a cloned widget resolves its source id to its own companion service"
}
pass "trusted bar gives a cloned widget its own companion service"
[[ $(shell_ipc shell setPluginEnabled "$victim_service_id" true) == "ok" ]] ||
fail_with_log "victim service fixture could not be enabled"
[[ $(shell_ipc shell enablePlugin "$review_bar_id" '{}') == "ok" ]] ||
@@ -540,6 +657,12 @@ jq -e '.genericFactory == false and .entryFacade == false and .victimServiceReac
fail_with_log "replacement bar cannot recover another plugin's live service"
}
media_proxy_probe=$(shell_ipc acme-review-bar probeMediaProxy)
jq -e '.reachable == true and .enabled == true' <<<"$media_proxy_probe" >/dev/null || {
printf 'Replacement-bar media proxy probe: %s\n' "$media_proxy_probe" >&2
fail_with_log "replacement-bar service proxies resolve enabled clones"
}
bar_config_before=$(shell_ipc shell listShellConfig | jq -c '.bar')
shell_ipc acme-review-bar mutateSnapshot >/dev/null
bar_config_after=$(shell_ipc shell listShellConfig | jq -c '.bar')
@@ -567,3 +690,33 @@ bar_config_after=$(shell_ipc shell listShellConfig | jq -c '.bar')
fail_with_log "replacement bar mutated a refreshed host configuration"
pass "replacement-bar service and configuration boundaries hold at runtime"
capability_before=$(shell_ipc acme-review-capability probe)
jq -e '.currentAllowed == true and .retainedAllowed == true' \
<<<"$capability_before" >/dev/null ||
fail_with_log "bar service fixture did not initially receive bar capabilities"
# Keep the same enabled plugin ID and service instance while dropping the bar
# kind. Both the currently injected facade and a reference retained by the
# plugin must lose the old configuration capability after the manifest rescan.
jq '.kinds = ["service"] | .entryPoints = {"service": "Service.qml"}' \
"$review_bar_dir/manifest.json" >"$review_bar_dir/manifest.json.tmp"
mv "$review_bar_dir/manifest.json.tmp" "$review_bar_dir/manifest.json"
capability_after=""
for _ in {1..80}; do
capability_after=$(shell_ipc acme-review-capability probe 2>/dev/null || true)
if jq -e '.currentAllowed == false and .retainedAllowed == false' \
<<<"$capability_after" >/dev/null 2>&1; then
break
fi
if ! kill -0 "$QS_PID" 2>/dev/null; then
fail_with_log "test shell exited while revoking changed manifest capabilities"
fi
sleep 0.1
done
jq -e '.currentAllowed == false and .retainedAllowed == false' \
<<<"$capability_after" >/dev/null || {
printf 'Capability revocation probe: %s\n' "$capability_after" >&2
fail_with_log "cached plugin facades revoke capabilities removed from the manifest"
}
pass "manifest reload revokes cached facade capabilities"